Curs 2 - Funcții, Tablouri și Operații pe Biți în C

În acest curs vom învăța despre funcții în C, structurile de date fundamentale precum tablourile și vom explora operațiile pe biți pentru optimizarea programelor.

1. Funcții în C

Funcțiile în C sunt blocuri de cod care pot fi apelate oriunde în program. Ele ajută la structurarea codului și la evitarea repetițiilor.

a) Definirea și Apelul unei Funcții

O funcție trebuie declarată înainte de a fi utilizată. Aceasta are o semnătură, un tip de retur și o listă de parametri.

#include <stdio.h>

// Declarația funcției
int aduna(int a, int b);

int main() {
    int rezultat = aduna(5, 7);
    printf("Suma: %d\n", rezultat);
    return 0;
}

// Definiția funcției
int aduna(int a, int b) {
    return a + b;
}

b) Transmiterea Parametrilor

În C, parametrii pot fi transmiși prin două moduri: prin valoare și prin referință (prin pointeri).

1. Transmitere prin valoare

Când se transmite prin valoare, o copie a variabilei este creată, iar modificările făcute în funcție nu afectează variabila originală.

#include <stdio.h>

void modifica(int x) {
    x = 10;
}

int main() {
    int a = 5;
    modifica(a);
    printf("Valoarea lui a: %d\n", a); // Rămâne 5
    return 0;
}

2. Transmitere prin referință (pointeri)

Prin utilizarea pointerilor, putem modifica direct valoarea variabilei originale.

#include <stdio.h>

void modifica(int *numar) {
    *numar = 10;
}

int main() {
    int x = 5;
    modifica(&x);
    printf("Noua valoare: %d\n", x); // Devine 10
    return 0;
}

c) Funcții Recursive

O funcție recursivă este o funcție care se apelează pe ea însăși pentru a rezolva o problemă de tip divide et impera.

#include <stdio.h>

int factorial(int n) {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}

int main() {
    int numar = 5;
    printf("Factorialul lui %d este %d\n", numar, factorial(numar));
    return 0;
}

d) Funcții Inline

Funcțiile inline sunt utilizate pentru a reduce overhead-ul apelului de funcție.

#include <stdio.h>

inline int patrat(int x) {
    return x * x;
}

int main() {
    printf("Patratul lui 4: %d\n", patrat(4));
    return 0;
}

e) Funcții Variadice

Funcțiile variadice pot accepta un număr variabil de argumente (de exemplu, `printf`).

#include <stdio.h>
#include <stdarg.h>

void afiseaza_numere(int numar, ...) {
    va_list args;
    va_start(args, numar);
    for (int i = 0; i < numar; i++) {
        printf("%d ", va_arg(args, int));
    }
    va_end(args);
    printf("\n");
}

int main() {
    afiseaza_numere(3, 10, 20, 30);
    return 0;
}

2. Clase de Stocare în C

Clasele de stocare determină unde și cât timp este păstrată o variabilă în memorie. Tipurile principale sunt:

a) `auto` - Variabile locale

Implicit, orice variabilă declarată într-o funcție este `auto`, ceea ce înseamnă că este stocată pe stiva funcției și este ștearsă automat la ieșirea din funcție.

void functie() {
    auto int x = 10; // Variabilă locală
}

b) `static` - Persistență în Memorie

Variabilele `static` rețin valoarea între apelurile funcției și sunt vizibile doar în fișierul curent.

#include <stdio.h>

void contor() {
    static int x = 0;
    x++;
    printf("x: %d\n", x);
}

int main() {
    contor();
    contor();
    contor();
    return 0;
}

c) `register` - Optimizare CPU

Acest modificator sugerează compilatorului să stocheze variabila într-un registru al CPU pentru acces rapid (dar compilatorul poate ignora această sugestie).

void functie() {
    register int x = 10;
}

d) `extern` - Variabile Globale

Variabilele `extern` sunt declarate într-un fișier și definite în alt fișier. Utilizate pentru partajarea datelor între module.

// fisier1.c
#include <stdio.h>
extern int global_var;
void afisare() {
    printf("Valoare global_var: %d\n", global_var);
}

// fisier2.c
#include <stdio.h>
int global_var = 10;
int main() {
    afisare();
    return 0;
}

3. Tablouri în C

Tablourile sunt structuri de date care stochează mai multe elemente de același tip într-o zonă contiguă de memorie.

a) Vectori

Vectorii sunt tablouri unidimensionale. Accesarea elementelor se face prin indici, începând de la 0.

#include <stdio.h>

int main() {
    int numere[5] = {1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++) {
        printf("%d ", numere[i]);
    }
    return 0;
}

b) Matrici

Matricile sunt tablouri bidimensionale, utile pentru reprezentarea unor structuri mai complexe, cum ar fi tabele sau imagini.

#include <stdio.h>

int main() {
    int matrice[2][2] = {{1, 2}, {3, 4}};
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", matrice[i][j]);
        }
        printf("\n");
    }
    return 0;
}

4. Operații pe Biți

Operațiile pe biți sunt utile pentru optimizarea programelor și interacțiunea cu hardware-ul la nivel scăzut.

a) Operatorii pe Biți

Operatorii pe biți sunt folosiți pentru manipularea individuală a biților dintr-un număr întreg.

&  // AND logic pe biți
|  // OR logic pe biți
^  // XOR logic pe biți
~  // Complement (NOT)
<< // Shift la stânga
>> // Shift la dreapta

b) Exemple de Utilizare

Exemplu de utilizare a operatorilor pe biți pentru manipularea valorilor numerice:

#include <stdio.h>

int main() {
    int x = 5, y = 3;
    printf("AND: %d\n", x & y);
    printf("OR: %d\n", x | y);
    printf("XOR: %d\n", x ^ y);
    printf("Shift Left: %d\n", x << 1);
    printf("Shift Right: %d\n", x >> 1);
    return 0;
}

c) Optimizarea Codului cu Operații pe Biți

Operațiile pe biți sunt foarte eficiente în optimizarea codului. De exemplu, în loc să înmulțim cu 2, putem folosi `x << 1`, ceea ce este mai rapid.

#include <stdio.h>

int main() {
    int x = 5;
    printf("x * 2: %d\n", x << 1);
    return 0;
}

Resurse suplimentare: