Documente online.
Zona de administrare documente. Fisierele tale
Am uitat parola x Creaza cont nou
 HomeExploreaza
upload
Upload




Structura programelor. Functii.

c


Structura programelor. Functii.

Filozofia (imprumutata din UNIX si) promovata de autorii C-ului ca si de standardul limbajului prevede descompunerea sarcinilor de realizat de catre un program in unitati/module functionale numite functii. Avantajele care deriva din aceasta filozofie se reflecta in



posibilitatea crescuta de refolosire a codului si

intr-o mai mare usurinta a modificarii/intretinerii codului aferent unei anumite sarcini.

Intr-un program C se pot distinge trei contexte/situatii in care se face referire la o functie:

declaratia functiei

definitia functiei

apelurile (apelarile) functiei

Declaratia unei functii se mai numeste si prototip al functiei.

Primul apel al oricarei functii ar trebui precedat de declaratia functiei. Standardul C nu impune acest lucru (spre deosebire de standardul pentru C++) dar in absenta unui prototip, primul apel al functiei este considerat ca declaratie din care se deduce tipul functiei ( in abasenta prototipului, tipul functiei este considerat a fi int). In ceea ce priveste argumentele, despre tipul si numarul acestora nu se face (compilatorul nu face!) nici o presupunere si se transmit functiei apelate asa cum sunt citate in apel.

Declaratii de functii

Sintaxa unei declaratii de functie este:

TIP nume_functie (TIPARGi

Unde

TIP este tipul valorii pe care functia o intoarce/returneaza in urma unui ap 323j95d el.

Nume_functie este numele sub care se vor face referiri (apeluri) la functie.

In paranteza se precizeaza lista tipurilor argumentelor functiei.

OBS.     1. Daca tipul declarat al functiei (TIP) este void, aceasta inseamna ca functia nu intoarce nici o valoare.

Lista de argumente poate fi vida, ceea ce inseamna ca la apelul functiei nu i se trimite nici un argument.

Nu este necesara (dar nici interzisa!) precizarea, in declaratie, de nume pentru argumentele functiei.

In definitia functiei:

Se precizeaza numele argumentelor formale

Se declara variabilele locale

Se descrie (printr-o secventa de instructiuni) actiunile de indeplinit la fiecare apel al functiei.

Se citeaza (intr-o instructiune return expresie) valoarea pe care functia o returneaza functiei apelante (numai daca tipul declarat al functiei este diferit de void)

Sintaxa unei definitii de functie:

TIP nume_functie(TIPARGi nume_argi)

Daca TIP este diferit de void, atunci ar trebui sa apara si o instructiune return in definitia functiei.

OBS. Definitia unei functii precizeaza CE SE FACE cu ARGUMENTELE primite la apel in scopul obtinerii VALORII ce se va RETURNA functiei apelante.

Intr-un program poate apare o singura definitie pentru fiecare functie.

Apelurile unei functii reprezinta mecanismul prin care controlul executiei este transferat unei functii, impreuna cu argumentele actuale, in scopul obtinerii unui anumit rezultat si/sau al efectuarii unor anumite actiuni.

Sintaxa unui apel de functie:

nume_functie(argument_actuali);/* apel de functie care nu intoarce nimic (are tipul void) */

var=nume_functie(argument_actuali);/* apel de functie care intoarce o valoare (are alt */

/* tip decat void)*/

Deasemenea apelul unei functii care intoarce o valoare poate apare in orice tip de expresii, inclusiv ca argument in apelul altei functii. Exemplu:

putchar(getchar( ));

Transmiterea argumentelor. Contextul de apel. Conventia de apel C

Argumentele se transmit de la functia apelanta la functia apelata prin intermediul stivei. Tot pe stiva se depune adresa de revenire din apel, adica adresa instructiunii la care se transfera controlul executiei dupa ce s-a terminat de executat functia apelata.

Argumentele se transmit prin valoare in ordinea inversa celei din lista de argumente, astfel incat primul argument din lista sa fie intotdeauna sub adresa de revenire. Daca functia apelata are si variabile locale, acestea se creaza tot pe stiva in ordinea declararii. Zona de pe stiva pe care se plaseaza valoarea argumentelor actuale, adresa de revenire si variabilele locale poarta denumirea de context de apel.

Acest mod (ordine) de transmitere a argumentelor catre functia apelata poarta denumirea de conventie de apel C.

Comunicarea intre functii

In programele C, functiile pot comunica prin:

Argumentele transmise de functia apelata functiei apelante

Valoarea returnata de functia apelata functiei apelante

Variabile globale.

Exemplu de comunicare prin variabile globale: program de evaluare a expresiilor aritmetice (+, -, *, /) introduse in notatie poloneza inversa (din [K&R], 4.3)

Textual, sarcina de indeplinit poate fi descrisa de urmatoarea secventa:

while (next operator or operand is not end-of-file indicator)

if (number)

push it

else if (operator)

pop operands

do operation

push result

else if (newline)

pop and print top of stack

else

error

#include <stdio.h>

#include <math.h> /* for atof() */

# define MAXOP 100 /* max size of operand or operator */

# define NUMBER '0' /* signal that a number was found */

int getop(char []);

void push(double);

double pop(void);

/* reverse Polish calculator */

main()

}

return 0;

}

OBS. Deoarece operatorii de adunare (+) si inmultire (*) sunt comutativi ordinea in care se "coboara" operanzii de pe stiva este irelevanta, in schimb, in cazul operatorilor de scadere (-) si impartire (/), care nu sunt comutativi, in varful stivei se afla operandul al doilea (scazatorul, respectiv impartitorul) care trebuie "coborate" intr-o variabila temporara (op2) pentru ca apoi ordinea operanzilor in expresie sa fie corecta (pop() - op2, respectiv pop() / op2).

#define MAXVAL 100 /* maximum depth of val stack */

int sp = 0; /* next free stack position */

double val[MAXVAL]; /* value stack */

/* push: push f onto value stack */

void push(double f)

/* pop : pop and return top value from stack */

double pop(void)

}

#include <ctype.h>

int getch(void);

void ungetch(int);

/* getop: get next operator or numeric operand */

int getop(char s[])

#define BUFSIZE 100

char buf[BUFSIZE]; /* buffer for ungetch */

int bufp = 0; /* next free position in buf */

int getch(void) /* get a (possibly pushed back) character */

void ungetch(int c) /* push character back on input */

Gestionarea aplicatiilor complexe. Proiecte

In scopul dezvoltarii si al gestionarii ulterioare a modificarilor in aplicatii complexe, de dimensiuni care nu fac rezonabila cantonarea tuturor functiilor intr-un singur fisier sursa, parti ale aplicatiei cu functionalitate distincta se pastreaza in fisiere sursa separate. Aceste fisiere sursa separate se pot modifica si compila independent fiind "legate" cu fisierele cod obiect rezultat din compilarea celorlalte componente in faza de link-editare. Ansamblul de fisiere sursa care implementeaza functionalitatea unei aplicatii poarta, in majoritatea mediilor de dezvoltare, denumirerea de proiect.

Exemplificare: programul anterior, care implementeaza un evaluator pentru expresii introduse de la stdin in notatia poloneza inversa poate fi realizat intr-o versiune in care diversele functionalitati sunt implementate in cate un fisier sursa separat. Pentru directivele de preprocesare si declaratiile necesare in mai multe fisiere sursa se creaza un fisier header care va fi inclus in fisierele sursa care au nevoie de continutul sau. (Exemplul este preluat din [K&R], 4.5).

calc.h:

----- ----- ------------

| #define NUMBER '0' |

| void push(double); |

| double pop(void); |

| int getop(char []);|

| int getch(void); |

| void ungetch(int); |

----- ----- ------------

main.c: getop.c: stack.c:

| #include <stdio.h> || #include <stdio.h> || #include <stdio.h> |

| #include <math.h> || #include <ctype.c> || #include "calc.h" |

| #include "calc.h" || #include "calc.h" || #define MAXVAL 100 |

| #define MAXOP 100 || getop() || void push(double) |----- ----- ------------| ... |

| } |

getch.c: | double pop(void) |

| #define BUFSIZE 100| ----- ----- ------------

| char buf[BUFSIZE]; |

| int bufp = 0; |

| int getch(void) |

| void ungetch(int) |

Initializarea variabilelor

Atribuirea unei valori unei variabile se realizeaza cu ajutorul operatorului de asignare ( ). Aceasta atribuire poate avea loc si in declaratia variabilei. In continuare vom face distinctie intre atribuirea unei valori in linia de decalaratie a unei variabile, pe care o vom numi initializare, si atribuirea intr-o expresie ulterioara declaratiei pe care o vom desemna ca asignare (sau atribuire) a unei valori la variabila respectiva. In cazul initializarii vom face distinctie intre initializarea automata (sau implicita) si initializarea explicita.

Initializarea automata (implicita ) se aplica variabilelor globale si variabilelor locale declarate static. Aceste variabile, care au ca punct comun faptul ca au clasa de memorare static, se initializeaza in mod automat cu 0.

Variabilele cu clasa de memorare auto nu se initializeaza automat, valoarea lor intiala (in absenta initializareii explicite) fiind valoarea prezenta, la momentul crearii variabilei, in zona de memorie (de pe stiva) unde aceasta e creata. Evident aceasta valoare scapa controlului programatorului!

int val;    /* variabila globala, initializata automat cu 0 */

f( ) /* definitia unei functii oarecare */

Initializarea explicita

Sintaxa pentru initializarea variabilelor scalare este:

TIP nume_var = expresie;

In cazul variabilelor globale, expresie trebuie sa fie o expresie constanta, pe cand in cazul variabilelor locale poate contine si variabile deja definite precum si parametri de apel ai functiei in care variabila este definita sau chiar apeluri de functii.

Ex.

int val = 1;

int contor=val; /*gresit: initializatorul nu este o expresie constanta */

f(int n)

Initializarea tablourilor

Ca si variabilele scalare, tablourile pot fi initializate. Elementele tablourilor declarate ca variabile globale sau ca variabile locale statice, se initializeaza implicit cu 0.

Initializarea explicita a tablourilor se realizeaza prin utilizarea unei liste de initializatori. Lista este specificata intre acolade, iar initializatorii sunt separati cu virgula.

Ex.

int a[5] = ;

float b[ ]=; /*tablou dimensionat prin numarul initializatorilor la 3 elemente!*/

int c[3] = ; /* greseala: prea multi initializatori!*/

int d[10] =; /* elementele neinitializate explicit se initializeaza implicit cu 0 */

Tablourile de caractere suporta doua modalitati de initializare. Una, comuna tuturor tablourilor:

char oras[30] = ;

OBS. Daca se doreste utilizarea continutului tabloului oras ca sir de caractere, atunci trebuie plasat si terminatorul de sir ('\0'):

char oras[30] = ;

In cazul in care valoarea unui tablou va fi utilizata ca sir de caractere, se initializeaza tabloul cu o constanta sir de caractere:

char oras[30]="BRASOV";

Functii recursive

Functiile C se pot apela pe ele insele, cu alte cuvinte, in C se pot defini functii recursive.

Orice functie recursiva trebuie sa contina o conditie de oprire a apelului recursiv. In absenta acesteia, sau daca aceasta nu e corecta, apelul recursiv va continua ducand la una din urmatoarele situatii:

a)      daca programul a fost compilat cu o optiune care sa includa cod pentru verificarea depasirii stivei, atunci, cand prin executia repetata a apelului recursiv contextele de apel succesive ajung sa epuizeze spatiul alocat pentru stiva, programul se termina fortat cu un mesaj de eroare: "Stack overflow".

b)      Daca programul a fost compilat fara optiunea de mai sus, atunci apelul recursiv continua, cu crearea contextelor de apel dincolo de limita zonei alocata pentru stiva distrugand eventualele date stocate in zonele respective. In acest caz:

Daca datele distruse nu sunt critice, apelul recursiv va continua pana la inceputul segmentului de date dupa care SP-ul (stack pointerul) vas indica din nou adresa de baza a stivei si procesul se reia.

Daca datele distruse sunt critice (influenteaza executia in continuare a programului) programul se termina anormal sau, mai frecvent, se blocheaza masina.

Exemple de functii recursive:

long fact(unsigned int n) /* functie recursiva pentru calculul factorialului*/

void printd(int n) /* printd: afiseaza n ca numar in baza 10 */

if (n / 10)

printd(n / 10);

putchar(n % 10 + '0');

}

void qsort(int v[], int left, int right) /* qsort: sorteaza crescator v[left],.,v[right] */

Functiile recursive sunt mai costisitoare (ca timp de executie si ca spatiu de memorie necesar) decat variantele iterative ale acelorasi algoritmi. Acest dezavantaj provine din necesitatea crearii cate unui context de apel la fiecare apel recursiv al functiei. Contextele de apel sunt prezente simultan in memorie - de unde consumul de memorie - iar pe de alta parte, crearea contextului de apel, care implica deplasarea de date inspre stiva este costisitoare ca timp.

Avantajul variantelor recursive ale unor algoritmi fata de variantele iterative rezida in naturaletea exprimarii, concizia si chiar eleganta lor.

Preprocesorul C

Preprocesorul C este (in standardul ANSI C) componenta initiala a compilatorului care are sarcina de a transforma codul sursa, conform unor directive de precompilare, inainte de compilarea prorpiu-zisa.

Directivele de precompilare se specifica pe linii separate care incep cu caracterlu #. Exemple de directive de precompilare:

Directiva de includere fisiere (header)

#include <nume_fisier_header.extensie>

sau

#include "nume_fisier_header.extensie"

Fisierele citate (fisiere text, continand de obicei macrodefinitii, prototipuri de functii, mai rar definitii de functii) se include in fisierul sursa in locul directivei inainte de si apoi noul fisier sursa rezultat este din nou supus preprocesarii in vederea expandarii macrodefinitiilor.

Macrodefinitii: definitie si expandare

Macrodefinitiile reprezinta o modalitate de substituire a unui sir de caractere cu un altul. Sintaxa generala este:

#define NUMEMACRO(argi) "Sir de substitutie"

OBS. Paranteza deschisa trebuie sa urmeze imediat (fara spatiu) dupa numele macroului!

Orice aparitie a NUMEMACRO(argactuali) in textul sursa va fi inlocuit cu sirul de substituie in care, in plus, fiecare aparitie a lui argi este inlocuita cu argactuali. Procesul acesta de substitutie poarta denumirea de expandare a macroului.

Ex.

#define PI 3.14159

#define TRUE 1

#define BEGIN {

dar si

#define max(A, B) ((A) > (B) ? (A) : (B))

#define square(x) (x)* (x)

Macrodefinitiile ofera o alternativa la definirea de functii, uneori utila prin economia de timp utilizata dar care trebuie utilizata cu grija datorita posibilelor efecte colaterale. De exemplu , macroul

#define square(x) x * x /* gresit */

"apelat"

square(n+1);

se expandeaza ca

n+1*n+1;

ceea ce e cu totul altceva decat ceea ce se dorea, adica

(n+1)*(n+1);

Solutie: argumentele se plaseaza intre paranteze atunci cand sunt citate in sirul de substitutie.

#define square(x) (x) * (x) /* corect */

Deasemenea utilizarea operatorilor de incrementare/decrementare la apelul macrourilor trebuie evitata atunci cand parametrul corespunzator apare de mai multe ori in sirul de substitutie. De exemplu:

a2=square(a++);

se va expanda ca

a2=(a++)*(a++);

ceea ce e diferit de

a2=(a)*(a);

a++:

Directive pentru conditionarea actiunilor preprocesorului/compilatorului

#idef NUME

#endif

#ifndef NUME

#endif

#if expresie_conditionala

#endif

#if !expresie_conditionala

#endif

Exemple:

#if !defined(HDR)

#define HDR

/* contents of hdr.h go here */

#endif

sau

#ifndef HDR

#define HDR

/* contents of hdr.h go here */

#endif

sau

if SYSTEM == SYSV

#define HDR "sysv.h"

#elif SYSTEM == BSD

#define HDR "bsd.h"

#elif SYSTEM == MSDOS

#define HDR "msdos.h"

#else

#define HDR "default.h"

#endif

#include HDR

.



Document Info


Accesari: 2523
Apreciat: hand-up

Comenteaza documentul:

Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta


Creaza cont nou

A fost util?

Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site


in pagina web a site-ului tau.




eCoduri.com - coduri postale, contabile, CAEN sau bancare

Politica de confidentialitate | Termenii si conditii de utilizare




Copyright © Contact (SCRIGROUP Int. 2024 )