PC Laborator 9
Obiective
În urma acestui laborator, studentul va fi capabil:
- să înțeleagă conceptul de funcție;
- să definească funcții, sub formă de prototip și implementare;
- să apeleze funcții;
- să utilizeze corect cuvintele cheie
void
șireturn
.
Funcții
Funcțiile sunt secțiuni ale unui program care se pot apela de mai multe ori, care pot modifica starea programului (prin modificarea variabilelor sau operații de IO - citire și scriere de date) și care pot întoarce un rezultat.
Definirea/ Declararea unei funcții
O funcție în C este definită în următorul fel:
tip_returnat nume_functie (lista_tip_argumente);
unde:
tip_returnat
definește tipul de date din care face parte valoarea returnată de funcție; dacă funcția nu întoarce nici o valoare (este folosit exclusiv pentru modificarea stării programului sau pentru IO), atunci se mai numește și procedură, iar tipul returnat estevoid
;nume_functie
reprezintă numele funcției care este folosit ulterior pentru apelul ei, și care respectă aceleași reguli ca orice identificator generic din C: poate conține exclusiv cifre, litere mici și mari, caracterul underscore (_), și nu poate începe cu cifră;lista_tip_argumente
reprezintă o listă de tipuri de date, separate prin virgulă, din care fac parte argumentele funcției; această listă poate fi goală (dacă funcția nu are argumente).
O declarație de funcție se numește și prototip.
Exemple de definiții de funcții
float max (float, float);
int inc (int);
void print (char[]);
int getAge ();
Implementarea unei funcții
Implementarea unei funcții în C se face în felul următor:
tip_returnat nume_functie (lista_argumente) { statement_1 statement_2 ... statement_n }
unde:
tip_returnat
reprezintă tipul returnat al funcției (vezi definiția unei funcții);nume_funcție
reprezintă numele funcției (vezi definiția unei funcții);lista_argumente
reprezintă o listă de definiții de variabile (de forma "tip_data nume_variabilă") care sunt argumentele funcției; această listă poate fi goală;statement_...
reprezintă una sau mai multe instructiuni (statement) C care implementează comportamentul funcției și care se execută de fiecare dată când se apelează funcția; corpul unei funcții poate fi gol (poate să nu conțină nici un statement), dar atunci apelul ei nu are nici un efect; dacă funcția nu este de tipvoid
, atunci obligatoriu ultima instrucțiune executată din funcție trebuie să fiereturn
, care setează valoarea întoarsă de funcție.
Exemple de implementări de funcții
float max (float a, float b) {
if (a >= b) return a;
return b;
}
int inc (int value) {
return value + 1;
}
void print (char[] string) {
printf("%s", string);
}
int getAge() {
return 20;
}
Observații
- O funcție nu este accesibilă/ apelabilă până nu este definită în program.
- Nu pot exista mai multe funcții cu același nume.
- O funcție poate avea cel mult o definiție și o implementare, dar definiția trebuie să fie plasată înaintea implementării.
- O funcție definită și ne-implementată poate fi apelată, și fișierul va compila cu succes, dar linker-ul nu va reuși să creeze fișierul executabil. Ca exemplu, încercați scrierea, compilarea și execuția acestui program:
#include <stdio.h> int inc(int); int main() { int n; printf("n = "); scanf("%d", &n); printf("n + 1 = %d\n", inc(n)); return 0; }
- Implementarea unei funcții ține loc implicit și de definiție, dacă aceasta nu există deja.
- Cuvântul cheie
return
oprește execuția funcției, indiferent dacă mai există sau nu instrucțiuni dupăreturn
. - Cuvântul cheie
return
poate fi folosit și în funcțiivoid
, dar urmat doar de punct și virgulă (;) și are ca efect ieșirea imediată din funcție. Ca exemplu, încercați scrierea, compilarea și execuția acestui program:#include <stdio.h> void printIfNotZero(int value) { if (value == 0) return; printf("S-a apelat functia printIfNotZero cu valoarea %d.\n", value); } int main() { printIfNotZero(1); printIfNotZero(0); printIfNotZero(2); printIfNotZero(3); printIfNotZero(0); return 0; }
- În C, argumentele unei funcții sunt "pass-by-value", în sensul că ele conțin valorile variabilelor cu care se apelează funcția, nu se identifică cu aceste variabile. Altfel spus, modificarea unui argument al funcției nu se propagă în afara funcției. Ca exemplu, încercați scrierea, compilarea și execuția acestui program:
#include <stdio.h> void inc(int value) { printf("In 'inc', la intrare, value este %d\n", value); value = value + 1; printf("In 'inc', la iesire, value este %d\n", value); } int main() { int n = 3; printf("Inainte de 'inc', n este egal cu %d\n", n); inc(n); printf("După 'inc', n este egal cu %d\n", n); return 0; }
Programe din mai multe fișiere
Programele complexe pot fi sparte în mai multe fișiere. În general, prin convenție, într-un fișier se scriu funcții care au legătură între ele, în sensul că sunt folosite pentru aceeași funcționalitate (de exemplu, funcții pentru criptare, sau funcții pentru IO, sau funcții pentru operații matematice, etc.). Pentru că aceste funcții pot teoretic fi folosite în multiple alte funcții din alte fișiere, sistemul cel mai des întâlnit pentru separarea acestor funcții este următorul:
- prototipurile funcțiilor se scriu într-un fișier cu extensia .h (header), iar acest fișier header este inclus acolo unde aceste funcții sunt apelate; aceste fișiere header trebuie obligatoriu să aibă gardă de dublă incluziune.
- implementările funcțiilor se scriu într-un fișier sursă .c, care include header-ul de mai sus, și care este compilat separat într-un object file.
- toate fișierele obiect se link-editează apoi într-un fișier executabil.
Exemplu
-
custom_math.h
#ifndef CUSTOM_MATH_H #define CUSTOM_MATH_H long long factorial (int); #endif
-
custom_math.c
#include <custom_math.h> long long factorial (int n) { long long result = 1; int i; if (n < 0) return 0; for (i = 2; i < n; i++) { result *= i; } return result; }
-
main.c
#include <stdio.h> #include <custom_math.h> int main() { int n; printf("n = "); scanf("%d", &n); printf("%d! = %d\n", n, factorial(n)); return 0; }
Compilarea unui executabil din mai multe fișiere
Pentru a obține executabilul pentru exemplul de mai sus, se compilează fiecare fișier sursă într-un object file:
gcc -c custom_math.c -o custom_math.o gcc -c main.c -o main.o
Apoi, se link-editează cele două surse împreună într-un executabil:
gcc custom_math.o main.o -o main
Makefile pentru compilarea unui executabil din mai multe fișiere
C_FILES=custom_math.c main.c
OBJ_FILES=$(C_FILES:.c=.o)
main: $(OBJ_FILES)
gcc $^ -o $@
%.o: %.c
gcc -c $^ -o $@
.PHONY: clear
clear:
rm -rf main $(OBJ_FILES)