Sunteți pe pagina 1din 20

7.

Preprocesorul
Preprocesorul, care nu se gsete n multe dintre limbajele de programare de nivel nalt,
furnizeaz instrumente ce permit programatorului s dezvolte programe ce sunt mai uor de
dezvoltat, mai uor de citit, de modificat i mai uor de transportat pe un sistem de calcul diferit.
Programatorul poate, de asemenea, utiliza preprocesorul pentru a individualiza literal limbajul C
pentru a corespunde unei aplicaii de programare particular sau pentru a satisface stilul de
programare propriu al programatorului.
Preprocesorul este o parte a procesului de compilare C, care recunoate declaraii speciale pe care le
poate conine un program C. Dup cum arat i numele, preprocesorul analizeaz aceste declaraii
nainte de a avea loc analiza propriu-zis a programului.
Declaraiile preprocesor sunt identificate prin prezena semnului #, care trebuie s fie primul
caracter din linie. Orice comand care ncepe cu caracterul # se numete directiv de
preprocesare, adic o comand pentru compilator. La compilare, directivele de preprocesare nu
sunt transformate n cod, ci doar controleaz modul de lucru al compilatorului.
Directivele de preprocesare nu sunt urmate de caracterul ;. Ele sunt scrise, ntotdeauna,
fiecare pe cte o linie separat i deci compilatorul tie c atunci cnd se termin linia se termin i
directiva de preprocesare.
Directiva #define
Una dintre primele utilizri ale acestei declaraii este pentru asignarea de nume simbolice la
constantele programului. De exemplu:
#define TRUE 1
definete numele TRUE i l face echivalent cu valoarea 1. Acest nume poate fi folosit, n
continuare, oriunde n program unde constanta 1 poate fi folosit. Preprocesorul va substitui fiecare
apariie a numelui TRUE cu valoarea asociat. Diferena ntre aceast definire i una de form
TRUE=1,este ca pentru aceast declaraie substituia lui TRUE cu 1 se face n timp ce se execut
programul, iar pentru declaraia anterioara substituia are loc la compilarea programului. Sintaxa
general:
#define NUME valoare
Exist o singur excepie de la aceast regul, i anume, dac numele respectiv este coninut ntr-un
ir de caractere. De exemplu n exemplul urmtor:
char *car_pointer = TRUE;
fixeaz referina lui car_pointer ctre irul TRUE i nu la 1.
Preprocesorul analizeaz fiierul nu nainte ca, compilatorul nsui, s examineze codul.
Iat ce realizeaz preprocesorul:
1. nlocuiete secvenele trigraf (trei semne) cu echivalentele lor.
2. Unete orice linie ce se termin cu caracterul \ (backslash) mpreun cu linia urmtoare
ntr-o singur linie.
3. mparte programul ntr-un flux de semne.
4. Elimin comentariile, nlocuindu-le printr-un singur spaiu.
5. Prelucreaz directivele preprocesor i expandeaz macrodefiniiile.
6. nlocuiete caracterele de evitare (escape) cu constante caracter i constantele ir de
caractere prin reprezentarea lor interna echivalent.
7. Concateneaz irurile de caractere constante adiacente.
Secvena trigraf
Pentru a prelucra seturi de caractere non-ASCII, urmtoarele secvene de trei caractere
(denumite trigraf) sunt recunoscute i tratate special, oriunde ele apar ntr-un program, chiar n
interiorul irurilor de caractere:
Trigraf
Semnificaie
??=
#
??(
[
??)
]
1

??<
??>
??/
??
??!
??-

{
}
\
^
|
~

Directiva #define poate fi utilizat n dou formate; primul format este urmtorul:
#define nume text
nume va fi substituit, n program, cu text, pana la sfritul liniei.
Cel de-al doilea format este:
#define nume (param_1,param_2, . . . ,param_n) text
Aceast form definete o macrodefiniie cu cele n argumente (parametri), fiecare dintre ele fiind un
identificator. Utilizrile urmtoare, n program, ale numelui, cu o list de argumente, determin
substituirea acestuia cu textul definit, argumentele apelului de macro nlocuind toate apariiile
parametrilor corespunztori din text. Operatorul # poate fi utilizat n directivele #define pentru a lua
argumentele. El este urmat de numele unui argument din macro.
Preprocesorul pune ghilimele n jurul valorii actuale (efective) transmise ctre macro, cnd
aceasta este utilizat, adic este transformat ntr-un ir de caractere.
Forma general pentru macrodefiniii este:
#define IdMacro (listaArg) (corpMacro)
unde argumentele se separ prin virgule, iar corpul macrodefiniiei nu conine spaii.
Referirea se face astfel:
IdMacro (lista_Expresii_Arg)
Efectul referii este substituirea prin corpul macrodefiniiei, n care argumentele sunt nlocuite
textual prin expresiile corespunztoare.
n corpMacro argumentele trebuie s fie ncadrate ntre paranteze (), altfel efectul nu este cel
dorit. Fac excepie argumentele ce reprezint identificatori de cmpuri din structuri.
n cazul n care corpul macrodefiniiei nu ncape pe o singur linie, el se va continua pe linia
urmtoare, linia curent fiind terminat cu \.
#define IdMacro (listaArg) (nceput_corp_Macro\
continuare_corp_Macro)
Nu se pot utiliza apeluri de funcii sau macrodefiniii n expresiile argumentelor care se repet n
corpul macrodefiniiei pentru a evita evaluarea acestora de mai multe ori:
#define patrat (x)
- este eronat
#define patrat ((x)*(x)- este corect
#define Max (a, b) ((a)>(b) ? (a) : (b))
pentru a determina maximul dintr-un grup de patru valori de forma:
x = Max (Max(x1, x2), Max(x3, x4));
este ineficient ntruct se substituie prin:
((x1) > (x2) ? (x1) : (x2)) > ((x3) > (x4) ? (x3) : (x4)) ?
((x1) > (x2) ? (x1) : (x2)) : ((x3) > (x4) ? (x3) : (x4))
mult mai eficient este:
t = Max(x1, x2);
x = Max(x3, x4);
x = Max(x, t);
Operatorul ## este, de asemenea, permis n directivele #define pentru a lua argumente. Este
urmat sau precedat de numele unui argument pentru macro. Preprocesorul ia valoarea ce este
transmis cnd se apeleaz macro i creeaz un singur semn (simbol) din argumentul pentru macro
i semnul ce-l urmeaz sau precede.
Exemplu:
2

#define printx(n) printf (x #n=%d\n, x##n);


cu apelul:
printx (10)
va furniza:
printf (x10=%d\n, x10);
Un nume astfel definit nu este o variabil, deci nu i putem atribui o valoare, numai dac
rezultatul substituirii valorii definite este de fapt o variabil.
De remarcat sintaxa sa speciala: nu se pune ; la sfritul declaraiei.
Aceste declaraii pot fi plasate oriunde n program, cu condiia ca ele s apar nainte de
referirea la numele respectiv de ctre program. Declaraia nu este locala, ea poate fi utilizat
oriunde n program.
De obicei, ns, aceste declaraii apar la nceputul programului (sau ntr-un fiier
INCLUDE). Identificatorii respectivi pot fi scrii cu litere mari sau mici (nu exist vreo restricie),
dar, de obicei, se utilizeaz litere mari (este o convenie printre programatorii C). Motivul este de a
vizualiza, distinct i repede, o valoare definit fa de o variabil.
Alt exemplu: definirea numrului maxim de elemente dintr-un tablou.
#define MAX_NR_ELEM 1000
float
tablou [MAX_NR_ELEM];
Dac dorim s mrim numrul de elemente de la 1000 la 2000 vom modifica doar aceast linie, n
loc s cutam prin tot programul toate declaraiile care fac referire la numrul de elemente din acest
tablou, dac nu s-ar utiliza aceast declaraie.
Un alt avantaj al acestor definiri este acela c permite realizarea de programe mai portabile
de la un calculator la altul. Uneori este necesar utilizarea de valori constante care depinde de tipul
calculatorului pe care se execut programul. De exemplu, aceasta poate fi legat de utilizarea unei
adrese de memorie, a unui nume de fiier sau de numrul de bii coninui n cuvntul unui
calculator.
Pentru a exemplifica, s considerm o funcie care realizeaz rotirea biilor unei valori de tip
int. Valoarea de tip int conine 16 bii, dar poate conine i 32 de bii pentru unele calculatoare. Din
acest motiv vom defini o constant INT_SIZE, care va fi definit n funcie de tipul calculatorului.
Funcia va avea dou argumente:
primul reprezint valoarea de rotit;
al doilea reprezint numrul de bii cu care este rotit valoarea; dac acest parametru
este pozitiv, valoarea va fi rotit la stnga, altfel va fi rotit la dreapta. Dac numrul de
rotaii, n, este mai mare dect constanta INT_SIZE, ntruct dup INT_SIZE rotaii se
obine valoarea iniial, se vor face n modulo INT_SIZE rotaii.
/* rotire de intregi */
#include <stdio.h>
void main( void )
{
unsigned int val1 = 0xabcdu, val2=0xff17u;
unsigned int rotire (unsigned int numar,int b);
printf ("%x\n", val1);
printf ("%x\n", val1 = rotire ( val1, 4 ));
printf ("%x\n", val1 = rotire ( val1, -4 ));
printf ("%x\n", rotire ( val2, 0 ));
printf ("%x\n", val2 = rotire ( val2, 36 ));
printf ("%x\n", val2 = rotire ( val2, -4 ));
printf("\n\n");
}
#define INT_SIZE 16
/***dependent de calculator***/
3

/* functia roteste un intreg fara semn la stanga sau drepta*/


unsigned int rotire (unsigned int valoare, int n )
{
unsigned int rezultat, biti;
/* se scaleaza contorul de deplasari la domeniul 0 - INT_SIZE-1*/
if (n > 0 )
n = n % INT_SIZE;
else
n = - (-n % INT_SIZE );
/* se pastreaza semnul pentru a sti directia de rotire*/
if ( n == 0 )
rezultat = valoare;
else if ( n > 0 )
/* rotire stanga */
{
biti = valoare >> ( INT_SIZE - n );
rezultat = valoare << n | biti;
}
else
{
n = -n;
biti = valoare << ( INT_SIZE - n );
rezultat = valoare >> n | biti;
}
return ( rezultat );
}
O definiie pentru preprocesor nu trebuie s fie o expresie valid, n C, n sensul strict, ct timp ea
este utilizat i rezultatul expresiei este valid. De exemplu definiia:
#define depl_stanga_6 <<6
nu pare o expresie valid sintactic, dar utilizarea ei ntr-o declaraie de forma:
x = y depl_stanga_6
este o expresie corect.
Alte exemple:
#defineSI
&&
#defineSAU
||
#defineEGAL
==
#definePI
3.141532654
#defineDOI_PI
2.0 * PI
Se pot defini astfel i expresii mai mari, reducndu-se necesitatea comentariilor n cadrul
programului. De exemplu expresia care determin dac o variabil an este an bisect sau nu:
if (an % 4 == 0 && an % 100 != 0 || an % 400 ==0 )
poate fi asociat unui nume astfel:
#define ESTE_AN_BISECT an % 4 == 0 && an %100 != 0 || an % 400 = 0
iar utilizarea va fi de forma:
if ( ESTE_AN_BISECT )
Se poate scrie o definiie care s aib unul sau mai multe argumente; de exemplu o funcie care
determin dac un an este bisect:
#define ESTE_AN_BISECT (y)
y % 4 == 0 && y % 100 != 0 || y % 400 == 0
iar utilizarea pentru variabila an va fi de forma:
if ( ESTE_AN_BISECT ( an ) )
Aceste definiii mai sunt denumite i MACRO-uri. Aceast terminologie este cel mai adesea
4

aplicat definiiilor ce au unul sau mai multe argumente. Spre deosebire de funcii la implementarea
n C a unei MACRO nu este important tipul argumentului, deci nu trebuie precizat. De exemplu, s
considerm un MACRO care determin ptratul unui numr:
#define PATRAT ( x ) x*x
Indiferent de tipul argumentului, int, long sau float acelai MACRO poate fi utilizat, ceea ce n
cazul funciilor nu este valabil: dac funcia a fost definit cu argument de tip int, ea nu poate fi
apelat cu argument de alt tip, de exemplu double.
Deoarece preprocesorul realizeaz o substituie literal a argumentului n definiia unui
macro, aceasta trebuie construit cu grij. De exemplu pentru macro anterior o declaraie de forma:
y = PATRAT ( a + 2 );
va fi evaluat, prin substituie lateral, astfel:
y = a + 2 * a + 2;
care evident nu va calcula (a + 2)2.
Pentru a trata adecvat astfel de situaii este necesar utilizarea de paranteze n definirea unui
macro:
#define PATRAT ( x ) ( x ) * ( x )
i pentru aceast form pot, ns, s mai apar unele probleme. De exemplu dac dorim s
calculm expresia:
100 / PATRAT ( 2 ) se obine:
100 / 2 * 2
al crui rezultat va fi 100 , n loc de 25.
Pentru a rezolva i astfel de situaii vom defini macro astfel:
#define PATRAT ( x ) ( ( x ) * ( x ) )
Din acest exemplu se poate trage concluzia de a utiliza att de multe paranteze ct este necesar
pentru a asigura c toate operaiile i asocierile sunt fcute n ordinea corect.
#include <iostream.h>
#define PATRAT(x) ((x)*(x))
void main(void)
{
for (int i = 1; i <= 10; i++)
cout << i << " la patrat este " << PATRAT(i) << endl;
}
Chiar i cu aceste precauii iat o situaie ce poate s apar:
PATRAT ( ++x )
care va deveni:
++x * ++x
caz n care x este incrementat de dou ori, o dat nainte de nmulire i o dat dup, deci:
++x * ++x = 5 * 6 = 30
Ultimul remediu, pentru aceast din urm problem, este de a evita utilizarea formei ++x ca
argument de macro. n general, nu se recomand utilizarea operatorilor de incrementare /
decrementare cu macr-uri. De remarcat, totui, c ++x poate fi un argument de funcie, deoarece el
este evaluat la valoarea 5 i apoi valoarea 5 este trimis funciei.
Alte exemple de macro-uri:
- determinarea valorii maxime dintre dou valori:
#define MAX ( a , b )
((a)>(b))?(a):(b)
- determinarea tipului unui caracter, dac este liter mic:
#define ESTE_LITERA_MICA (x) ( ( ( x ) >= 'a' ) && ( ( x ) <= 'z' ) )
- utilizarea acestei macrodefiniii, ntr-o alt macrodefiniie, pentru a converti un caracter de
la liter mic la liter mare:
#define LITERA_MARE ( x ) ( ESTE_LITERA_MICA ( x ) ? ( x ) - 'a' +'A' : ( x ) )
care poate fi folosit ntr-un program astfel:
while ( *sir != '\0' )
{
*sir = LITERA_MARE ( *sir );
++sir;
5

}
care va transforma literele mici n litere mari pentru un ir de caractere referit de variabila
sir.
Operatorul #:
Dac se plaseaz semnul # n faa unui parametru ntr-o macrodefiniie, preprocesorul va
crea o constant ir de caractere pentru argumentul macroului, cnd este utilizat macro.
De exemplu, definiia:
#define str ( x ) #x
va determina pentru urmtoarea utilizare:
str ( testare )
s fie expandat n:
testare
sau, de exemplu, funcia:
printf ( str( salut!\n ) );
este echivalenta cu
printf (salut!\n);
Dac n irul de caractere apare semnul el va fi precedat de \, astfel:
str(salut) va furniza
\salut\
Un exemplu tipic de utilizare este urmtoarea definiie de macro:
#define printint ( va r)
printf ( #var=%i\n, var )
pentru care o utilizare de forma:
printf ( numar )
va fi expandat n:
printf ( numar =%i\n, numar );
care dup concatenarea celor dou iruri de caractere va deveni:
printf ( numar=%i\n, numar );
Operatorul ##
Operatorul ## este utilizat n macrodefiniii pentru a lega dou semne mpreun. El este
precedat sau urmat de numele unui parametru de macro. Preprocesorul ia argumentul actual din
macro, care este specificat la apelarea macrodefiniiei, i creeaz un singur semn din acel argument
i orice urmeaz sau precede semnul ##.
De exemplu, s presupunem c avem o lista de variabile x1 pn la x100. Se poate defini o
macrodefiniie care s aib ca argumente valorile de la 1 la 100 i s afieze variabila
corespunztoare, astfel:
#define printx ( n )
printf ( %i\n, x##n )
definiie ce citete x##n i care precizeaz c se iau semnele ce apar nainte i dup ##, litera x i
argumentul n i realizeaz un singur semn.
Astfel apelul:
printx(50);
va fi expandat n:
printf ( %i\n,x50 );
sau, dac se utilizeaz definiia anterioar, putem defini noua macro astfel:
#define printx ( n ) printint ( x##n )
care pentru un apel de forma:
printx(20);
va genera:
printf ( x20=%i\n, x20 );
printf ( x20=%i\n, x20 );
Directiva #include
Dac dorim includerea unui set de macrodefiniii n fiecare program putem, n loc s le
definim la nceputul fiecrui program, s le depunem pe toate ntr-un fiier separat i apoi s-l
includem n orice program care are nevoie de aceste definiii utiliznd directiva #include.
Utilizarea i facilitile acestei directive sunt cel mai bine folosite n dezvoltarea de
programe care au fost mprite n module de subprograme separate.
Dac la elaborarea unui program lucreaz mai muli programatori, atunci includerea de
fiiere furnizeaz un mijloc de standardizare; fiecare programator va utiliza aceleai definiii, care
au aceleai valori; n plus, se elimin o surs de erori i o pierdere de timp pentru introducerea
acestor definiii n fiecare modul care are nevoie de ele.
Mai mult, n momentul n care trebuie fcut o modificare a unei definiii (pentru o anumit
6

structur de date), ea poate fi fcut numai ntr-un singur loc i anume n fiierul de inclus.
Fiierul care va colecta toate aceste definiii va fi definit printr-un nume urmat de un punct i
litera h, astfel:
nume_fisier.h
extensia h este utilizat, de obicei, pentru astfel de fiiere, denumite fiiere antet - header. Fiierele
antet constau din declaraii preprocesor. Includerea definiiilor din acest fiier n programul curent
se face astfel:
#include nume_fisier.h
directiva mai poate avea i forma, de exemplu:
#include <stdio>
Dac se utilizeaz < >, preprocesorul va cuta fiierul ntr-unul sau mai multe directoare de
sistem, standard. De exemplu:
#include bool.h - caut n directorul curent
#include a:\dir1\bool.h - va cuta pe drive-ul a, n directorul dir1.
Exemplu:
definim valorile booleane: TRUE=1 i FALSE=0
/* bool.h - fisierul ce contine aceste declaratii */
#define BOOLEAN int
#define TRUE 1
#define FALSE 0
/*

bool.c - programul numara caracterele "spatii albe" dintr-un text


introdus de la tastatura, terminat cu EOF (CTRL-Z,RETURN)

*/
#include <stdio.h>
#include "bool.h"
main( )
{
int ch, count = 0;
BOOLEAN spatiu_alb( char );
while ( (ch = getchar( ) ) != EOF )
if (spatiu_alb ( ch) ) /* echivalenta cu if ( spatiu_alb (ch) == TRUE ) */
count ++;
printf ( "S-au intalnit %d spatii albe\n", count );
}
BOOLEAN spatiu_alb ( char c )
{
if ( c == ' ' || c == '\n' || c == '\t' )
return ( TRUE );
else
return ( FALSE );
}
n acest exemplu am creat un nou tip BOOLEAN, deoarece BOOLEAN este pur i simplu de
tipul int. Aceast declaraie, pentru funcia logic definit, este de a aminti utilizatorului c funcia
este utilizat pentru calcule logice i nu aritmetice. Se poate utiliza o macrodefiniie n loc de a
defini o funcie pentru a defini spaiul alb.

7.1. Programe alctuite din mai multe module


Cea mai simpl metod de utilizare a mai multor funcii este ca acestea s fie plasate n
acelai fiier. Apoi se compileaz acel fiier exact ca un fiier cu o singur funcie. De obicei, ns,
deoarece funciile sunt realizate i testate separat ele pot fi n fiiere separate. n acest caz se va
proceda, n funcie de sistem, astfel:
7

pentru sistemele UNIX:


cc fis1.c
fis2.c
unde fis1.c i fis2.c sunt fiierele ce conin funcii. n urma acestei comenzi se va produce un singur
fiier executabil a.out, precum i dou fiiere obiect fis1.o i fis2.o.
Dac ulterior se modific doar fis1.c, nu i fis2.c, se poate compila primul, fis1.c, cu fiierul
obiect al celui de-al doilea fis2.o, utiliznd comanda:
cc fis1.c fis2.o
Pentru PC-uri utiliznd un compilator de C se va crea un fiier test cu extensia prj (de la proiect
project) i se vor pune n el numele fiierelor utilizate pentru program. De exemplu fis.prj va
conine liniile:
fis1.c
fis2.c
Apoi se utilizeaz meniul Project i se selecteaz fiierul proiect dorit, dup care se va
selecta meniul Run sau Compile.
Pentru ca modulele, coninute n fiiere separate, s se poat efectiv comunica, se pot utiliza
mai multe metode. Dac o funcie dintr-un fiier necesit un apel al unei funcii, coninut n alt
modul, atunci apelul acestei funcii se face n mod normal i argumentele pot fi transmise i
returnate n mod obinuit. Bineneles, c n fiierul care apeleaz funcia trebuie, ntotdeauna,
inclus o declaraie a prototipului funciei astfel nct compilatorul s tie tipurile argumentelor i
eventual, tipul valorii returnate.
Dup cum s-a menionat, n absena oricrei informaii despre o funcie, compilatorul va
presupune c returneaz o valoare de tip int i va converti argumentele de tip short sau char la int,
iar argumentele de tip float la tipul double, cnd funcia este apelat.
Chiar dac sunt specificate compilatorului mai multe module pentru compilare, n acelai
timp n linia de comand, compilatorul le va compila separat, pe fiecare individual. Deci toate
informaiile necesare, comunicrii ntre module, trebuie furnizate de utilizator.
O alt metod de comunicare ntre module aflate n fiiere diferite, pe lng funcii, este
utilizarea variabilelor externe. Variabilele externe reprezint o extensie a conceptului de variabile
globale. O variabil extern, asemntor celor globale, poate fi accesat i modificat de alte
module. n cadrul modulului care se dorete s aib acces la variabila extern, aceasta este declarat
n modul normal, dar va fi precedat de declaraia de cuvntul extern. De exemplu:
extern int numar;
Orice modul care dorete acces la aceast variabil, trebuie s conin o astfel de variabil.
Modulul n care e definit variabila, va conine o declaraie obinuit (fr a fi precedat de
extern):
int numar;
sau dac este iniializat, sub forma:
int numar = 0;
Se poate declara variabila extern n exteriorul tuturor funciilor i poate fi iniializat:
extern int numar = 0 ;
Cele dou modaliti sunt mutual exclusive. Adic, cuvntul extern poate fi omis ntr-un
loc, printre fiierele surs; dac nu se omite declaraia extern n nici un modul, atunci ntr-un loc
trebuie asigurat valoarea iniial (ca n ultimul exemplu).
S considerm dou module:
/* main modul principal */
int n=10;
main ()
{
printf (%i, n);
functie ( );
printf (%i\n, n);
}

/* functie.c alt modul */


extern int n;
functie ( )
{
n=n*n;
}

Deci o variabil definit n exteriorul unei funcii nu este numai o variabil global, dar este
i o variabil extern. n multe situaii dorim ns s definim o variabil global, dar care s nu fie i
8

variabil extern, adic s fie o variabil global, dar local pentru acel modul, astfel ca doar
funciile din acel modul s aib acces la ea, nu i din alte module. Acest deziderat se poate realiza
cu declaraia static, n exteriorul funciilor dintr-un modul; n cest fel vor avea acces la variabil
toate funciile din modulul respectiv, dar nu i din alte module.
O funcie este considerat n cazul implicit de tip extern; ea poate fi declarat i de tip static,
dac dorim ca ea s fie apelat numai n cadrul aceluiai fiier n care apare. n acest caz se plaseaz
declaraia static naintea declaraiei antetului funciei:
static double radacina (double x);
S considerm dou module:
modul1.c
#include <stdio.h>
double x;
static double rezultat;
static void patrat_loc (void)
{
double patrat (void);
x = 4.0;
rezultat = patrat ( );
}
main ( )
{
patrat_loc ( );
printf (%f\n, rezultat);
}

modul2.c
extern double x;
double patrat (void);
{
return (x*x);
}

Funcia main ( ) apeleaz funcia patrat_loc ( ), definit n acelai modul (modul1.c), care la
rndul ei apeleaz funcia patrat ( ) din alt modul (modul2.c).
Funcia patrat_loc(), fiind declarat static, poate fi apelat doar din modul1.c. n acest
modul sunt definite dou variabile globale:
- x, care poate fi apelat de orice modul;
- rezultat de tip static, care poate fi apelat doar din modul1.c.
Variabila x este declarat n modul2.c de tip extern, astfel s poat accesa aceast variabil definit
n modul1.c.

8. Agoritmi de sortare
n acest capitol vom prezenta civa algortimi de sortare (ordonare) dintre cei mai
reprezentativi i mai des utilizai. n capitolele anterioare au mai fost prezentai doi algoritmi de
sortare foarte cunoscui, i anume: algorimul de sortare prin metoda inversiunilor (sau bulelor, cum
este denumit) i algoritmul de ordonare Donald Shell; asupra acestora nu vom mai reveni n acest
capitol.
Sortare prin selecie direct
Aceast metod se bazeaz pe urmtorul principiu, de exemplu pentru ordonare cresctoare:
1. Se selecteaz termenul cu cea mai mic valoare.
2. Se schimb acesta cu primul termen (ai).
Apoi se repet operaiile cu cele n-1 elemente rmase, i aa mai departe pn rmne un singur
termen (cel mai mare). Deci, pentru sortarea cresctoare a unui ir, algoritmul este urmtorul:
for (i = 1; i < n-1; i++)
9

{
* se determin indicele celui mai mic element, k, dintre a[i], . . . ,a[n];
* se interschimb a[i] cu a[k];
}
Iat ntreg programul care sorteaz prin selecie direct:
/* sel_dir.c - programul ordoneaza un vector prin selectie directa,
utilizand pentru aceasta determinarea minimului si a pozitiei lui,
la fiecare iteratie si realizand o singura inversiune intre acest
minim si valoarea a[i], corespunzatoare iteratiei curente;
se determina si numarul de comparatii si inversiuni realizate
*/
#include<stdio.h>
#include <conio.h>
int ncomp=0, ninv=0; /* numar de comparatii si inversiuni */
void sort_sel_direct ( double a[], int n )
/* functia sorteaza un vector prin metoda de selectie directa */
{
double x;
int i, j, k;
for ( i = 0 ; i < n-1 ; ++i )
{
k = i; /* initializare indice, k, si elementul minim, x */
x = a[i];
for ( j = i+1 ; j < n ; ++j, ncomp++ ) /* determinare minim si indicele lui din sir */
if (a[j] < x)
{
k = j;
x = a[k];
}
a[k] = a[i]; /* interschimbare minim cu primul din subsirul sursa, */
a[i] = x;
/* adica cu primul din subsirul neordonat */
ninv++;
}
}
void main(void)
{
double sir[100];
int ne,i, nl=0;
clrscr();
printf("Numar elemente:");
scanf("%d",&ne);
for(i=0;i<ne;i++) /* citirea elementelor sirului de ordonat */
{
printf("sir(%d)=",i+1);
scanf("%lf",& sir[i]);
}
sort_sel_direct(sir,ne); /* ordonarea sirului prin selectie directa */
for(i=0;i<ne;i++)
/* afisarea sirului ordonat */
{
10

printf(" sir(%2d)=%5.1lf",i+1,sir[i]);
nl++; /* actualizare contor numar de valori afisate pe o linie */
if ( nl % 5 == 0 ) /* daca s-au afisat 5 valori pe o linie se trece pe */
printf("\n"); /* o linie noua */
}
printf("\n");
printf("Ordonarea s-a realizat prin %d comparatii si %d inversiuni\n",
ncomp, ninv);
}
Sortare prin inserare direct.
Aceast metod este utilizat de juctorii de cri. Elementele irului (deci, crile de joc)
sunt mprite n dou subiruri:
- un subir destinaie a1, a2, a3, . . ., ai-1, care este ordonat pe msura crerii lui, i
- un subir surs ai, ai+1, ai+2, . . ., an;
Iniial irul destinaie, deci cel ordonat, este format doar din primul element al irului, a1. La
fiecare pas (iteraie), ncepnd cu i = 2, elementul i din irul surs este luat din acesta i transferat n
irul destinaie prin inserare pe poziia corespunztoare, dup compararea cu elementele irului
destinaie.
irul destinaie este parcurs de la dreapta la stnga, deoarece la fiecare comparaie, dac
elementul de inserat, cel din irul surs, nu trebuie inserat pe poziia respectiv n irul destinaie,
elementul cu care s-a comparat, din irul destinaie, este deplasat cu o poziie la dreapta pentru a
face loc noului element, de introdus n irul destinaie. Din cazurile particulare, adic situaiile n
care noul element trebuie inserat pe prima sau pe ultima poziie, rezult, n mod evident, aceast
condiie.
Deci algoritmul este urmtorul:
for ( i = 1; i < n ; i++)
{
x = a [i];
/* elementul curent de inserat; */
se insereaz x pe poziia corespunztoare n a1, a2, a3, . . ., ai-1,
parcurgnd acest subir de la dreapta la stnga
}
Programul pentru sortarea prin inserare direct este prezentat n continuare.
/* ins_dir.c prog. ordonare cresc. sir, utilizand metoda de inserare directa, adica:
se considera un sir ordonat, format initial din primul element din sirul initial,
si un sir neordonat, format din restul elementelor sirului, din care se iau pe
rand celelalte elemente si li sa cauta, de la dreapta la stanga, pozitia in
sirul ordonat, construit tot in sirul initial. */
#include <stdio.h>
#include <conio.h>
int ncomp=0, nins=0; /* numar de comparatii si inserari/ deplasari */
void sort_ins_direct (double a[], int n)
{/* functia de sortare prin inserare directa */
double x;
int i, j;
for (i=1; i<n; ++i)
{
x=a[i]; /* elementul curent de inserat */
j=i-1; /* dimensiunea sirului destinatie */
11

while (x < a[j] && j >= 0)


{
/* se cauta pozitia de inserare */
a[j+1]=a[j];
j--;
ncomp++; nins++;
}
a[j+1]=x;
/* inseare element curent */
}
}
void main()
{
double sir[100];
int ne, i;
clrscr();
printf("Numar elemente:");
scanf("%d", &ne);
for(i=0; i<ne; i++) /* citirea elementelor sirului de sortat */
{
printf("sir(%d)= ", i+1);
scanf("%lf", &sir[i]);
}
sort_ins_direct (sir, ne); /* ordonarea sirului prin inserare directa */
printf("\n Sirul ordonat:\n");
for(i=0; i<ne; i++) /* afisare sir ordonat */
{
printf(" sir(%d)=%5.2lf ", i+1, sir[i]);
if ( (i+1) %5 == 0 ) /* daca s-au afisat 5 valori din sir pe o linie */
printf("\n");
/* se trece pe o linie noua */
}
printf("\n");
printf("Ordonarea s-a realizat prin %d comparatii si %d deplasari\n",
ncomp, nins);
}
Sortare prin inserare binar
O metod mai rapid pentru determinarea poziiei de inserare a elementului curent din irul
surs (ai) n irul destinaie, pentru algoritmul anterior este cutarea acestei poziii prin metoda
binar.
Aceast cutare binar const n a mpri irul destinaie n dou subiruri de dimensiuni
egale (deci se njumtete irul destinaie) i se continu progresiv aceast operaie pe subirul
destinaie cruia i aparine elementul de inserat, pn se identific aceast poziie. Vom nota cu s
respectiv d indicii de nceput (stnga) i sfrit (dreapta) ai subirului n care se continu cutarea,
iar cu m vom nota indicele elementului din mijlocul subirului.
/*
ins_bin.c - prog. de ordonare cresc. sir, utilizand metoda de inserare binara,
adica: se considera un sir ordonat, format initial din primul element din
sirul initial, si un sir neordonat, format din restul elementelor sirului,
din care se iau pe rand celelalte elemente si li se cauta, de la dreapta
la stanga, pozitia in sirul ordonat, construit tot in sirul initial.
12

Cautarea se face utilizand metoda "binara".


*/
#include<stdio.h>
#include <conio.h>
int ncomp=0, nins=0; /* numar de comparatii si inserari/deplasari */
/*
functia de sortare prin inserare binara
*/
void sort_ins_binara ( double a[], int n )
{
double x;
int i, j, s, d, m;
for (i=1; i<n; ++i)
{
x=a[i]; s=0; d=i-1;/* initializari: x-elementul de inserat */
while (s <= d) /* s, d-limitele stanga/dreapta pentru subsir dest */
{ncomp++;
m=(s+d)/2; /* mijlocul subsirului */
if (x < a[m]) /* in functie de apartenenta elementului x */
d=m-1; /* la subsirul din stanga sau dreapta */
else
/* se stabileste noua limita din stanga/dreapta */
s=m+1; /* a noului subsir, in care se cauta */
}
/* dupa determinarea pozitiei de inserare a elem.*/
for (j=i-1; j>=s; --j, nins++) /* se deplaseaza tot sirul cu */
a[j+1]=a[j]; /* o pozitie la dreapta, de la pozitia */
a[s]=x;/* de inserare pana la sfarsit, dupa care */
/* se insereaza noul element, x */
}
}
void main(void){
double sir[100]; int ne,i;
clrscr();
printf("Numar elemente:");
scanf("%d", &ne);
for(i=0; i<ne; i++) /* citire sir de ordonat */
{
printf("sir(%d)= ", i+1);
scanf("%lf", &sir[i]);
}
printf("Sirul ordonat este: \n");
sort_ins_binara(sir , ne); /* apelul functiei de sortare binara */
for (i=0;i<ne;i++)
/* afisare sir ordonat */
{
printf(" sir (%2d) = %lf \n", i+1, sir[i]);
if ((ne+1)%4==0)
/* tiparesc cate 4 valori pe linie */
printf("\n");
}
printf("\n");
printf("Ordonarea s-a realizat prin %d comparatii si %d deplasari\n",
ncomp, nins);
}
13

Sortare prin metoda inversiunilor (metoda bulelor)


Aceast metod const n compararea primei valori cu toate celelalte din vector, i
interschimbarea (inversarea) lor dac este cazul, adic dac nu sunt ordonate. Dup prima
parcurgere valoarea minim (sau maxim, depinde de sortarea dorit, cresctoare sau
descresctoare) va ajunge n prima poziie. La parcurgerea urmtoare se consider subirul ncepnd
de la al doilea element care va fi comparat i inversat, dac este cazul, cu elementele rmase (n-2).
La ultima parcurgere se vor compara ultimele dou elemente din ir, deci n total sunt n-1
parcurgeri. Deci algoritmul este urmtorul:
for ( i = 2; i < n ; i++ )
for ( j = n; j >= i ; i-- )
if (a[j-1] > a[j])
{
x = a[j-1];
a[j-1] = a[j];
a[j] = x;
}
n continuare este prezentat ntregul program ce ordoneaz un ir de valori utiliznd aceast
metod.
/* sortbule.c - programul ordoneaza un sir de valori prin
metoda inversiunilor (bulelor) - forma clasica
*/
#include <stdio.h>
#include <conio.h>
int ncomp=0, ninv=0; /* numar de comparatii si inversiuni/ deplasari */
void sort_met_bulelor (double a[], int n)
/*
functia sorteaza un vector prin metoda inversiunilor (bulelor) */
{double x;
int i, j;
for (i=1; i<n; i++)
for (j=n-1; j>=i; j--, ncomp++)
if (a[j-1] > a[j])
{
x=a[j-1];
a[j-1]=a[j];
a[j]=x;
ninv++;
}
}
void main(){
double sir[100];
int ne,i, nl=0;
clrscr();
printf("Numar elemente:");
scanf("%d", &ne);
for(i=0; i<ne; i++) /* citirea elementelor sirului de ordonat */
{printf("sir(%d)=",i+1);
scanf("%lf", &sir[i]);
}
14

sort_met_bulelor (sir, ne); /* ordonarea sirului prin selectie directa */


for(i=0; i<ne; i++)
/* afisarea sirului ordonat */
{
printf("sir(%2d)=%5.1lf ", i+1, sir[i]);
nl++; /* actualizare contor numar de valori afisate pe o linie */
if (nl % 5 == 0) /* daca s-au afisat 5 valori pe o linie */
printf("\n"); /* se trece pe o linie noua */
}
printf("\n");
printf("Ordonarea s-a realizat prin %d comparatii si %d deplasari\n",
ncomp, ninv);
}
Dezavantajele acestei metode:
- indiferent de ordinea din ir acesta este parcurs tot de n-1 ori, de fiecare dat de la i la n,
cu i parcurgnd intervalul 2,n-1, cu toate c dac la o parcurgere (deci pentru un i
oarecare) nu a aprut nici o modificare (inversiune), nu mai trebuie reluat parcurgerea
pentru urmtorul i.
- se poate memora poziia pn la care subirul format (obinut) este ordonat, deci i-ul la
care a avut loc ultima inversiune. Dac se memoreaz aceast valoare a lui i ntr-o
variabil k , la care s-a fcut ultima inversiune, rezult, n mod evident, c subirul
format din elementele a1, a2, a3, . . ., ak, este ordonat, deci nu mai este necesar s fie
parcurs i la urmtoarele treceri.
- ultima observaie se refer la urmtorul fapt:
- dac valoarea minim se afl pe ultima poziie, iar restul irului este ordonat,
dintr-o singur parcurgere se va ordona ntregul ir;
- dac ns pe prima poziie se afl vaoarea maxim, iar restul irului este
ordonat, vor fi necesare n-1 parcurgeri ale irului pentru a-l ordona.
- din aceast ultim observaie se constat c direcia de parcurgere a irului poate scurta
numrul de parcurgeri ale irului i numrul de comparaii, deci poate scurta durata de
ordonare. De aici rezult i a 3-a mbuntire a acestui algoritm: alternarea direciilor de
parcurgere la etape succesive.
Algoritmul rezultat (cu aceste mbuntiri) este denumit ShakerSort i este prezentat n
continuare.
d=n-2; s=0; k=d; /* initializari pentru limita din stanga (s), dreapta (d) */
do
/* iar k reprezint pozitia la care s-a realizat ultima inversiune */
/* ncomp, ninv numar de comparatii, inversiuni realizate */
{for (i=s; i<=d; i++, ncomp++)
/* parcurgerea de la stnga la dreapta */
if (v[i]>v[i+1])
/* dac este cazul se inverseaza elementele neordonate */
{t=v[i];
v[i]=v[i+1];
v[i+1]=t;
k=i;
/* se reine pozitia ultimei inversiuni */
ninv++;}
d=k-1;
/* se modifica corespunzator limita din dreapta */
for (i=d; i>=s; i--, ncomp++)
/* parcurgerea de la dreapta la stnga */
if (v[i]>v[i+1])
/* dac este cazul se inverseaza elementele neordonate */
{t=v[i];
v[i]=v[i+1];
v[i+1]=t;
15

k=i;
/* se reine pozitia ultimei inversiuni */
ninv++;}
s=k+1;
/* se modifica corespunzator limita din stanga */
}while(s <= d);
Acest algoritm este mai rapid dect sortrile prin metoda inserrii directe sau cel al seleciei.
Sortarea unui vector, ce conine indicii vectorului iniial (de sortat)
n cazul n care datele care trebuie ordonate sunt reprezentate pe foarte muli octei, cum este
cazul unor structuri largi, atunci este de preferat, indiferent de algoritmul de sortare utilizat, s nu se
realizeze inversiunile (ordonarea) ntre componentele vectorului respectiv (tab[], ca n exemplul
urmtor), deoarece vor necesita mult timp de execuie pentru inversarea lor, ci s se ordoneze un
vector ce conine indicii componentelor din vectorul iniial (index[]); binneles, comparaiile
necesare ordonrii se vor face ntre valorile cheie (info) dup care se face ordonarea, avnd, ns, ca
indice indicele din tabloul de indici care se ordoneaz (adresare indirect). Iniial acest tablou
(index) va conine valorile indicilor tabloului de ordonat (de fapt aceste valori sunt iniial 0, 1, 2, 3,
). Indiferent de metoda de sortare utilizat ordonarea se va face dup o anumit cheie (informaie
coninut n componentele tabloului, n exemplul urmtor aceasta este numit medie) asupra
tabloului de indici (index), care va fi utilizat ca indici pentru tabloul iniial (tab[index[j]]). Se va
utiliza o adresare indirect, adic indicii tabloului ce trebuie sortat vor fi luai din tabloul de indici
(tab[index[j]]), iar sortarea se va face asupra acestuia i nu direct asupra tabloului iniial. Deci, n
final, tabloul iniial, cel de sortat (tab), va rmne neschimbat, dar tabloul cu indici (index), ai
tabloului de ordonat, va fi ordonat dup valorile (cmpurile) pe care acesta le selecteaz din tabloul
iniial. Vom exemplifica aceast metod de ordonare, des utilizat, a tabloului de indici asociat
tabloului de valori, utiliznd algoritmul de sortare shakersort.
/* sortindx.c - se sorteaza indecsii unui tablou de tip structura,
care contine date pentru un numar de studenti (nume, prenume si media)
asociat acestui tablou definim un tablou cu indecsii tabloului de studenti;
vom sorta studentii dupa medii prin intermediul tabloului de indecsi,
iar sortarea indicilor se va face utiliznd algoritmul ShakerSort
*/
#include <stdio.h>
#define DIM 100
#include <conio.h>
struct tip_struct
{
char nume_prenum [DIM];/* numele si prenumele unui student*/
float medie;
};
void main(void)
{
struct tip_struct stud [100];/* consideram un tablou de studenti */
int
ind[100]; /* vectorul ce contine indecsii tabloului initial, si ce
va fi ordonat in functie de valorile vectorului de ordonat "tab" */
int
i, ns;
float f;
void ShakerSort (struct tip_struct tab[], int index[], int n);
clrscr();
16

printf("Numarul de studenti:");
scanf("%d", &ns);
f=0.0; fflush(stdin);
for (i=0; i<ns; i++) /* citire date studenti: nume, prenume si media */
{
printf("Numele si prenumele studentului %d: ", i+1);
gets(stud[i].nume_prenum);
printf("Media notelor sale: ");
scanf("%f", &f);
stud[i].medie=f;
while (getchar() != '\n');
}
for (i=0; i<ns; i++)
/* initializarea tabloului de indici */
ind[i] = i;
ShakerSort (stud, ind, ns); /* sortarea vectorului */
printf("Lista ordonata cu numele / prenumele si media studentilor\n");
for (i=0; i<ns; i++)
printf("%s %.2f\n", stud[ind[i]].nume_prenum,
stud[ind[i]].medie);
}
void ShakerSort (struct tip_struct tab[], int index[], int n)
{
int i, s, d, k, t;
d=n-2; s=0; k=d;
do
{
for (i=s; i<=d; i++)
if (tab[index[i]].medie < tab[index[i+1]].medie)
{
t=index[i];
index[i]=index[i+1];
index[i+1]=t;
k=i;
}
d=k-1;
for (i=d; i>=s; i--)
if (tab[index[i+1]].medie > tab[index[i]].medie)
{
t=index[i];
index[i]=index[i+1];
index[i+1]=t;
k=i;
}
s=k+1;
} while (s <= d);
}
Pentru testare se poate simplifica programul considernd, pentru aceasta, un vector de valori
numerice i n acest caz dispare, din exemplul anterior, cmpul info, deci comparaiile respective
vor fi de forma urmtoare: if (tab[index[i]] < tab[index[i+1]]), respectiv if (tab[index[i+1]] >
tab[index[i]]).
17

Sortare partajat (QuickSort)


Acest algoritm mai este denumit i algoritm de sortare rapid (QuickSort), autorul su fiind
Hoare.
Metoda are la baz faptul c interschimbrile ar fi de preferat s se realizeze peste distane
ct mai mari pentru a accelera sortarea, fiind astfel mult mai eficient.
Algoritmul este urmtorul: se alege un termen la ntmplare (de obicei cel din mijloc, pe
care-l vom nota x); parcurgem (scanm) vectorul de la stnga la dreapta pn se gsete un termen
a[i] > x i apoi se parcurge invers, de la dreapta la stnga, pn se gsete un termen a[j] < x.
Acum se schimb ntre ei cei doi termeni i se continu aceast scanare i interschimbare pn
cnd cele dou direcii de scanare se ntlnesc undeva la mijloc.
Rezultatul dup aceast parcurgere este un ir care conine n partea stng elemente mai
mici dect x, iar partea dreapt conine elemente mai mari dect x; x acioneaz ca o santinel pentru
ambele direcii de scanare. Deci irul este mprit n dou dup valoarea lui x n dou partiii, una
cu valori mai mici dect x, iar cealalt cu valori mai mari.
Scopul nostru nu este de a gsi partiii ale vectorului ci de a-l sorta. Dup aceast prim
scanare dac se reia procesul de scanare asupra celor dou partiii i apoi asupra partiiilor astfel
obinute, i aa mai departe pn cnd o partiie const dintr-un singur element, moment n care se
va obine sortarea vectorului.
Deci funcia QuickSort este recursiv i este descris n continuare.
QuickSort ( int s, int d)
{
/* limitele partiiei la stnga (s) i la dreapta (d) */
i = s; j=d;
/* iniializarea celor dou scanri (I) i respectiv(j) */
x = a [(s +d)/2 ];
/* santinela din mijlocul partiiei curente */
do
{
while (a[i] < x) i++;
while (a[j] > x) j--;
if (i <= j)
/* se schimb ntre ele cele dou elemente */
{
t = a[i]; a[i] = a[j]; a[j] = t;
i++; j--;
}
}
while (i <= j); /* i se continu pn se ntlnesc cele dou scanri */
/*
s-a mprit irul iniial n dou subiruri (partiii), primul cu elemente mai mici dect x, iar cel
dex, iar cel de-al doilea cu ele,ente mai mari
*/
if ( s < j ) QuickSort ( s , j ); /* dup care se continu scanarea celor dou partiii */
if ( i < d ) QuickSort (i , d ); /* astfel obinute, pn se apeleaz o partiie de 1 element */
}
iar apelul funciei se va face sub forma:

QuickSort ( 1 , n ).

ntreg programul de ordonare utiliznd algoritmul QuickSort este prezentat n continuare.


/* QuickSor.c - sortarea unui vector utilizand algoritmul QuickSort */
#include<stdio.h>
#include<conio.h>
#define DIM 100
18

typedef int VECTOR [DIM];


void cit_vect (int n, VECTOR v)
{int i;
for(i=0; i<n; i++)
{printf("v[%d]=", i+1);
scanf("%d", &v[i]);}
}
void scrie_vect (int n, VECTOR v)
{int i;
for(i=0; i<n; i++)
printf("v[%d]=%d\n", i+1, v[i]);
}
void QuickSort (VECTOR a, int s, int d)
{int i, j, m, x, temp;
i=s;
j=d;
m=(s+d)/2;
x=a[m];
do
{while (a[i]<x) i++;
while (a[j]>x) j--;
if (i<=j)
{temp=a[i];
a[i]=a[j];
a[j]=temp;
i++;
j--;
}
}while(i<=j);
if(s < j) QuickSort (a, s, j);
if(i < d) QuickSort (a, i, d);
}
void main(void)
{int n;
VECTOR v;
clrscr();
printf("Programul ordoneaza (sorteaza) un vector cu alg. QuickSort.\n");
printf("Dimensiune vector, n=");scanf("%d", &n);
cit_vect (n, v);
QuickSort (v, 0, n-1);
printf("\nVectorul ordonat este:\n");
scrie_vect (n, v);
getch();
}

B I B LI O G RAFI E
19

1. Cristea, C. Gimale, E. Kalisz, A. Pnoiu "Limbajul C standard", Ed. Teora, 1992.


2. Stephen G. Kochan "Programming in ANSI C", Hayden Books, fifth printing-1992.
3. Deitel H. M., Deitel P.J. "C++: How tio program", Prentice Hall, fourth edition-2003.

20

S-ar putea să vă placă și