FAQC: Funcții și variabile

Oameni faini

Written by:

Am vorbit deja în articolul precedent despre pointeri și despre faptul că aceștia indică o adresă la care se află o variabilă în memorie. Totuși, de ce sunt ei folositori? Când avem nevoie să folosim pointeri? Pentru a răspunde acestor întrebări și pentru a vedea exemple concrete de utilizare a pointerilor, vom introduce în această săptămână conceptul de funcție. Ne propunem să discutăm despre funcții și variabile. Astfel, detaliem ce sunt funcțiile, rolul lor în programe și mai cu seamă folosirea lor în C.

Vom vedea cum putem apela funcțiile, ce sunt parametrii și cum se modifică o variabilă în cadrul unei funcții. Astfel, vom discuta și despre noțiunile de variabile locale, globale și statice și despre cum să le folosim și când. Să o luăm cu începutul!

Ce este o funcție?

O funcție este un set de instrucțiuni care realizează o anumită sarcină. Ca și structură, orice funcție trebuie să fie alcătuită din două părți: antet (sau semnătură) și corp. Orice program în C conține cel puțin o funcție, acest lucru fiind datorat faptului că orice program trebuie să aibă o funcție numită main. Vom studia care sunt cele două componente pe exemplul funcției main. În general, aceasta are structura similară cu următorul exemplu:

1
2
3
4
5
6
#include <stdio.h>

int main() {
    printf ("Hello from the main function!\n");
    return 0;
}

OUTPUT
Hello from the main function!

Să luam pe rând cele două componente ale funcției:

  • Antetul funcției : int main ()
    Acesta ne oferă următoarele informații:

    • Funcția va returna o valoare de tip int (vom vedea imediat ce înseamnă acest lucru)
    • Numele funcției este main
    • Funcția nu primește niciun parametru
  • Corpul funcției
    În exemplul nostru, acesta este alcătuit din instrucțiunea care printează mesajul “Hello from the main function!” și instrucțiunea de return, ce spune că funcția returnează valoarea 0.
    Corpul funcției poate conține orice instrucțiuni și este delimitat de acolade ce deschid după semnătura funcției și se închid după terminarea instrucțiunilor din corpul ei.

Funcția dată ca exemplu mai sus returnează o valoare. În cazul funcției main, spunem că returnăm valoarea 0 dacă programul s-a încheiat cu succes. Funcția main este funcția principală, apelată la începutul execuției programului.

Orice funcție poate sa returneze o valoare, de orice tip, fie el de bază sau nu (tipurile sunt de bază sau derivate). În exemplul următor, funcția func_with_return returnează o valoare de tipul float:

1
2
3
4
float func_with_return () {
    float pi = 3.14;
    return pi;
}

Totuși, o funcție poate să nu returneze nicio valoare, caz în care în locul tipului de date se va pune cuvântul cheie void, iar instrucțiunea de tipul „return valoare” va lipsi:

1
2
3
4
5
void func_with_no_return () {
    int a, b;
    a = 5;
    b = 10;
}

Pentru a vedea cum sunt folosite aceste valori returnate, sau funcțiile care nu returnează nimic, să vedem cum sunt folosite (apelate) funcțiile într-un program.

Cum se poate apela o funcție?

Când folosim o funcție, spunem că o apelăm sau chemăm. Când este apelată, se execută setul din instrucțiuni din corpul ei. O funcție poate fi executată atât din funcția main, cât și din oricare altă funcție.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int f() {
    return 10;
}

int main() {
    int a;
    a = f();
    printf("Valoarea lui a este: %d", a);

    return 0;
}

OUTPUT

Valoarea lui a este: 10

Observăm în programul de mai sus faptul că valoarea de return a funcției f este stocată în variabila a din funcția main.

Până acum am arătat exemple de apel de funcții, însă acestea nu aveau parametri. Să vedem în continuare ce sunt parametrii și de ce avem nevoie de ei.

Parametrii unei funcții

Fiecare funcție reprezintă o entitate separată cu propriul ei set de variabile declarate. Astfel, o funcție nu se poate folosi de variabilele declarate în altă funcție. Programul de mai jos va genera o eroare de compilare, deoarece funcția f nu are acces la nicio variabilă care să se numească ‘a’ sau ‘b’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void f() {
    int result;
    result = a + b;
    printf("Suma a + b este: %d\n", result);
}

int main() {
    int a = 4;
    int b = 2;

    f();

    return 0;
}

OUTPUT COMPILE-TIME

error: ‘a’ undeclared (first use in this function)
result = a + b;

error: ‘b’ undeclared (first use in this function)
result = a + b;

Datorită faptului că variabilele a și b sunt declarate în funcția main, le numim variabile locale. Acest lucru înseamnă că o funcție nu poate avea acces la ele, cât timp nu sunt date ca și parametri. Parametrii sunt variabilele date unei funcții în antetul ei și în funcție de cum este apelată, valoarea lor se poate schimba.

Astfel, atunci când declarăm o funcție și vrem ca aceasta să aibă parametri, specificăm în interiorul parantezelor din antet tipul și numele lor astfel:

void f (int a, int b)

Funcția f cu semnătura de mai sus va avea doi parametri de tipul int, care se vor numi a, respectiv b, adică vor fi folosite în funcție ca și variabile cu denumirile specificate în antet.

Fiecare parametru va avea un tip de date (char, short, int, etc) și un nume, iar parametrii vor fi separați prin virgulă. După cum probabil ai observat, declararea unui parametru se aseamănă cu declararea unei variabile obișnuite. În interiorul funcției ce are parametri, aceștia se vor comporta precum niște variabile obișnuite la care funcția are acces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void f (int a, int b) {
    int result;
    result = a + b;
    printf("Suma a + b este: %d.\n", result);
}

int main() {
    int a = 4;
    int b = 2;

    f(a, b);

    return 0;
}

OUTPUT

Suma a + b este: 6.

O funcție cu parametri poate fi apelată și cu valori efective. Spre exemplu, apelul f (5, 6) este un apel valid al funcției f. Aceasta va afișa suma 11. Totodată, parametrii puteau să aibă orice denumire, nu neapărat a și b, întrucât aceștia nu sunt propriu-zis variabilele din funcția main.

Parametrii pe care îi trimitem unei funcții sunt de fapt copii ale variabilelor cu care apelăm funcția. De aceea se spune despre C că este un limbaj de programare „pass by value”. Atunci când dăm o variabilă ca parametru unei funcții, de fapt îi dăm valoarea acesteia, o copie a ei.

Rolul variabilelor în funcții

Variabile locale

Așa cum am văzut în exemplul de mai sus, nu putem accesa din interiorul unei funcții variabilele declarate în alte funcții. După cum spuneam, o variabilă declarată în interiorul unei funcții se numește variabilă locală. Variabilele locale au următoarele proprietăți:

  • Deși diferă de la un compilator la altul, variabilele locale pot fi inițializate cu garbage, adică o valoare random, dacă nu sunt inițializate în mod explicit. Astfel, e recomandat ca variabilele locale să fie inițializate, mai ales dacă se efectuează operații asupra lor.
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main() {
    int a;

    a += 5;
    printf("Valoarea lui a este: %d", a);

    return 0;
}

OUTPUT

Valoarea lui a este: 1448617366

  • Acestea pot folosite doar în interiorul funcției în care au fost declarate, după ce au fost declarate.
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main() {
    a = 10;

    printf("%d", a);
    int a;

    return 0;
}

OUTPUT

error: ‘a’ undeclared (first use in this function)
a = 10;

  • Nu putem modifica valoarea unei variabile locale într-o funcție dând-o ca parametru unei alte funcții. Întrucât unei funcții i se dă ca parametru o copie a unei variabile, modificările aduse acelei variabile nu vor fi vizibile în exteriorul funcției căreia i-a fost dată ca parametru.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void f(int a) {
    a = a + 2;
    printf("Valoarea lui a in interiorul functiei f este: %d \n", a);
}

int main() {
    int a = 5;
    printf("Valoarea lui a inainte de apelul functiei f este: %d \n", a);

    f(a);
    printf("Valoarea lui a dupa apelul functiei f este: %d \n", a);

    return 0;
}

OUTPUT

Valoarea lui a inainte de apelul functiei f este: 5

Valoarea lui a in interiorul functiei f este: 7

Valoarea lui a dupa apelul functiei f este: 5

Astfel, valoarea variabilei a din interiorul funcției main nu s-a schimbat. Deși copia ei din interiorul funcției s-a modificat, această modificare nu este vizibilă în exteriorul funcției f.

Totuși, se poate ca o variabilă locală să își păstreze valoarea în interiorul funcției (dar nu și în exterior) pe parcursul programului? Răspunsul este DA. Aceste variabile se numesc variabile statice.

Variabile statice

O variabilă este statică dacă în declararea ei este folosit cuvântul cheie static. Astfel, variabilele statice au următoarele proprietăți:

  • Sunt inițializate cu 0.
  • Acestea pot fi folosite doar în interiorul funcției în care au fost declarate, după ce au fost declarate
  • Ca și în cazul variabilelor locale, acestea nu pot fi modificate de alte funcții. Totuși, variabilele statice au proprietatea importantă că modificările aduse asupra lor în funcția în care au fost declarate persistă în interiorul acelei funcții. Să urmărim acest aspect în următorul exemplu:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void f () {
    static int i = 0;
    i = i + 2;
    printf ("Valoarea lui a este: %d\n", i);
}

int main () {

    f();
    f();
    f();

    return 0;
}

OUTPUT

Valoarea lui a este: 2.
Valoarea lui a este: 4.
Valoarea lui a este: 6.

După cum se observă, după fiecare apel de funcție, valoarea variabilei a se modifică, iar schimbarea rămâne persistentă și la următorul apel al funcției f.

Totuși, cum putem face ca această modificare să rămână vizibilă oriunde în program?

O metodă prin care putem să folosim și să modificăm aceeași variabilă din mai multe funcții, iar modificarea să fie vizibilă oriunde în program, este folosirea variabilelor globale.

Variabile globale

Pentru ca o variabilă sa fie globală, aceasta trebuie declarată în afara unei funcții. De obicei acestea se declară înaintea scrierii oricărei funcții. Acestea au următoarele proprietăți:

  • Sunt inițializate cu 0.
  • Pot fi folosite în cadrul oricărei funcții, fără să fie declarate din nou.
  • Modificările aduse asupra variabilelor globale sunt vizibile și în exteriorul funcțiilor în care acestea sunt folosite.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int a;

void f() {
    a = a + 2;
    printf("Valoarea lui a in interiorul functiei f este: %d \n", a);
}

int main() {
    a = 5;
    printf("Valoarea lui a inainte de apelul functiei f este: %d \n", a);
   
    f();
    printf("Valoarea lui a dupa apelul functiei f este: %d \n", a);
   
    return 0;
}

OUTPUT

Valoarea lui a inainte de apelul functiei f este: 5

Valoarea lui a in interiorul functiei f este: 7

Valoarea lui a dupa apelul functiei f este: 7

Folosirea variabilelor globale este adesea nerecomandată. Orice funcție are acces la o variabilă globală, astfel că aceasta se poate modifica de oriunde din program, iar astfel variabila poate fi folosită în restul programului cu o valoare incorectă, din neatenție. Un alt motiv este acela că un cod ce conține variabile globale va fi mai greu de înțeles și de urmărit.

Cum am putea accesa și modifica o variabilă din interiorul mai multor funcții, dar fără să apelăm la variabile globale? Răspunsul este: pointeri.

Pointerii în funcții

Să presupunem că avem variabila locală a declarată în funcția main și vrem să o modificăm în interiorul funcției f. De asemenea, dorim ca modificarea să fie vizibilă și în funcția main.

Pentru a realiza acest lucru, folosim un pointer ce va reține adresa variabilei locale a și va fi dat ca parametru funcției f. Deși funcția va primi o copie a adresei variabilei a (pentru ca C este un limbaj „pass by value”), acea adresă va fi una validă și va putea fi folosită pentru a accesa variabila locală a din funcția main.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void f (int* a) {
    *a += 3;
}

int main() {
    int a = 2;
    int* ptr = &a;

    f(ptr);

    printf("Valoarea lui a este: %d", a);

return 0;
}

OUTPUT

Valoarea lui a este: 5

Prin folosirea pointerilor putem restricționa funcțiile care au acces la o variabilă, conducând astfel la un cod mai ușor de urmărit și de întreținut.

Astfel, atunci când dorim modificarea unei variabile locale în mod persistent, putem trimite ca parametru adresa ei. Atunci când se produc modificări la o adresă în memorie, acele modificări sunt vizibile și în exteriorul funcției ce a primit adresa ca și parametru.

Sumar. Cuvinte cheie

Ajungem astfel la finalul unui nou articol din seria FAQC și recapitulăm noțiunile prezentate în această săptămână.

Ne amintim că o funcție:

  • Este un set de instrucțiuni ce îndeplinesc un anumit task;
  • Are două componente: antet (semnătură) și corp (bloc de instrucțiuni);
  • Poate să returneze o valoare, al cărei tip de date îl menționăm în antet, sau poate să nu returneze nimic, dacă în loc de tipul de date folosim cuvântul cheie void în antetul ei;
  • Poate fi apelată fără parametri sau să aibă parametri pe care să îi folosească în interiorul ei ca și variabile locale.

Variabilele locale:

  • Sunt inițializate cu valori garbage (random);
  • Pot fi folosite doar în interiorul funcției în care au fost declarate, după ce au fost declarate;
  • Pot fi date ca parametru altor funcții, însă modificarea lor în alte funcții nu va fi vizibilă în exterior.

Variabilele statice:

  • Sunt inițializate cu 0;
  • Pot fi folosite doar în interiorul funcției în care au fost declarate, după ce au fost declarate;
  • Ca și la variabilele locale, modificarea lor în alte funcții nu va fi vizibilă în exterior, însă modificarea lor în funcția în care au fost declarate va persista la fiecare apel al funcției respective.

Variabilele globale:

  • Sunt inițializate cu 0;
  • Pot fi folosite oriunde în program, în orice funcție, după ce au fost declarate în afara funcțiilor;
  • Modificările asupra lor din orice funcție vor fi vizibile de-a lungul programului.

Dacă un parametru este un pointer, modificările aduse la valoarea de la adresa indicată de pointer în interiorul funcției vor fi vizibile în exterior.

Cuvinte cheie: funcție, variabilă, pointer

Spread the love