Cos'è una Funzione?

Una funzione è un blocco di codice riutilizzabile che esegue un compito specifico. Permettono di organizzare il codice secondo il principio DRY (Don't Repeat Yourself).

CAnatomia di una funzione
#include <stdio.h>

// PROTOTIPO: dichiara che la funzione esiste (prima del main)
// tipo_ritorno nome(tipo param1, tipo param2);
double media(double a, double b);
int massimo(int x, int y);
void saluta(char nome[]);  // void = non ritorna nulla

int main() {
    printf("Media: %.2f\n", media(10.5, 7.3));
    printf("Max:   %d\n",   massimo(42, 17));
    saluta("Giulia");
    return 0;
}

// DEFINIZIONE: il corpo della funzione
double media(double a, double b) {
    return (a + b) / 2.0;
}

int massimo(int x, int y) {
    return (x > y) ? x : y;
}

void saluta(char nome[]) {
    printf("Ciao, %s!\n", nome);
    // Nessun return (o return; senza valore)
}
Prototipo vs Definizione Il prototipo dice al compilatore che la funzione esiste e qual è la sua firma. La definizione contiene il corpo. Se la definizione viene prima del main, il prototipo non è necessario.

Passaggio per Valore vs per Riferimento

In C, i parametri sono passati per valore di default: la funzione riceve una copia. Per modificare la variabile originale, si usa il puntatore.

CPer valore vs per puntatore
#include <stdio.h>

// Passaggio per VALORE: la modifica non si propaga
void tenta_incremento(int n) {
    n += 10;  // Modifica la copia locale, non l'originale
    printf("Dentro la funzione: n = %d\n", n);
}

// Passaggio per PUNTATORE: la modifica si propaga
void incrementa(int *n) {
    *n += 10;  // Dereferenzia il puntatore e modifica l'originale
}

// Scambia due valori tramite puntatori
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5;
    tenta_incremento(x);
    printf("Dopo tenta_incremento: x = %d\n\n", x);  // Ancora 5!

    incrementa(&x);  // Passa l'indirizzo di x
    printf("Dopo incrementa: x = %d\n\n", x);  // 15

    int a = 10, b = 20;
    printf("Prima swap: a=%d, b=%d\n", a, b);
    swap(&a, &b);
    printf("Dopo swap:  a=%d, b=%d\n", a, b);

    return 0;
}
Output
Dentro la funzione: n = 15
Dopo tenta_incremento: x = 5

Dopo incrementa: x = 15

Prima swap: a=10, b=20
Dopo swap:  a=20, b=10

Scope e Variabili Statiche

CScope locale, globale e static
#include <stdio.h>

int globale = 100;  // Visibile in tutto il file

void dimostra_static() {
    static int contatore = 0;  // Inizializzata UNA SOLA VOLTA, persiste
    contatore++;
    printf("Chiamata n. %d\n", contatore);
}

void dimostra_scope() {
    int locale = 10;       // Visibile solo in questa funzione
    {
        int interno = 20;  // Visibile solo in questo blocco
        printf("interno = %d\n", interno);
    }
    // printf("%d", interno);  // ERRORE! interno non esiste qui
    printf("locale = %d, globale = %d\n", locale, globale);
}

int main() {
    dimostra_scope();

    dimostra_static();  // Chiamata 1
    dimostra_static();  // Chiamata 2
    dimostra_static();  // Chiamata 3

    return 0;
}

Ricorsione

Una funzione è ricorsiva quando chiama sé stessa. Ogni funzione ricorsiva deve avere un caso base che ferma la ricorsione.

CRicorsione — fattoriale, Fibonacci, MCD
#include <stdio.h>

// Fattoriale: n! = n * (n-1)!   Base: 0! = 1
long long fattoriale(int n) {
    if (n <= 0) return 1;        // Caso base
    return n * fattoriale(n - 1); // Chiamata ricorsiva
}

// Fibonacci: f(n) = f(n-1) + f(n-2)   Base: f(0)=0, f(1)=1
long long fibonacci(int n) {
    if (n <= 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}

// MCD (Massimo Comun Divisore) — algoritmo di Euclide
int mcd(int a, int b) {
    if (b == 0) return a;
    return mcd(b, a % b);
}

// Potenza: base^esp
double potenza(double base, int esp) {
    if (esp == 0) return 1.0;
    if (esp < 0)  return 1.0 / potenza(base, -esp);
    return base * potenza(base, esp - 1);
}

int main() {
    for (int i = 0; i <= 10; i++)
        printf("%d! = %lld\n", i, fattoriale(i));

    printf("\nFibonacci (0-10): ");
    for (int i = 0; i <= 10; i++)
        printf("%lld ", fibonacci(i));
    printf("\n");

    printf("\nMCD(48, 18) = %d\n", mcd(48, 18));
    printf("2^10 = %.0f\n", potenza(2.0, 10));

    return 0;
}
Attenzione alla ricorsione profonda Ogni chiamata ricorsiva usa stack memory. Con N troppo grande (es. fibonacci(50) senza memoization) la ricorsione è esponenzialmente lenta e può causare stack overflow. In quei casi preferisci soluzioni iterative o usa la memoization.

Puntatori a Funzione

In C le funzioni hanno un indirizzo in memoria. Un puntatore a funzione ti permette di passare funzioni come argomenti ad altre funzioni.

CPuntatori a funzione
#include <stdio.h>

int somma(int a, int b)  { return a + b; }
int diff(int a, int b)   { return a - b; }
int prodotto(int a, int b){ return a * b; }

// Funzione che accetta un puntatore a funzione come parametro
int applica(int a, int b, int (*operazione)(int, int)) {
    return operazione(a, b);
}

int main() {
    // Sintassi: tipo (*nome_puntatore)(tipi_parametri)
    int (*op)(int, int);

    op = somma;
    printf("Somma:    %d\n", op(10, 5));

    op = diff;
    printf("Diff:     %d\n", op(10, 5));

    op = prodotto;
    printf("Prodotto: %d\n", op(10, 5));

    // Array di puntatori a funzione
    int (*operazioni[3])(int, int) = {somma, diff, prodotto};
    char *nomi[] = {"somma", "diff", "prodotto"};

    for (int i = 0; i < 3; i++) {
        printf("%s(7, 3) = %d\n", nomi[i], operazioni[i](7, 3));
    }

    // Passaggio come argomento
    printf("\napplica(10,5,somma)    = %d\n", applica(10, 5, somma));
    printf("applica(10,5,prodotto) = %d\n", applica(10, 5, prodotto));

    return 0;
}
🏋️ Esercizi
Pratica con funzioni, ricorsione e passaggio parametri.
1
Funzioni matematiche base
FacileFunzioni

Scrivi le funzioni: perimetro_cerchio(r), area_cerchio(r), volume_sfera(r). Poi scrivi un main che le chiami con r=5.0 e stampi i risultati con 4 decimali. Usa la costante M_PI da math.h (o definiscila tu).

C
#include <stdio.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

double perimetro_cerchio(double r) { return 2.0 * M_PI * r; }
double area_cerchio(double r)      { return M_PI * r * r; }
double volume_sfera(double r)      { return (4.0 / 3.0) * M_PI * r * r * r; }

int main() {
    double r = 5.0;
    printf("Raggio: %.1f\n", r);
    printf("Perimetro cerchio: %.4f\n", perimetro_cerchio(r));
    printf("Area cerchio:      %.4f\n", area_cerchio(r));
    printf("Volume sfera:      %.4f\n", volume_sfera(r));
    return 0;
}
2
È palindromo?
MedioRicorsione / stringa

Scrivi una funzione ricorsiva palindromo(str, inizio, fine) che controlli se una stringa è palindroma (uguale letta al contrario). Es: "racecar", "level", "madam". Testa la funzione con almeno 5 stringhe.

Il caso base: inizio >= fine (stringa di 0 o 1 caratteri è sempre palindroma).
C
#include <stdio.h>
#include <string.h>

int palindromo(char str[], int inizio, int fine) {
    if (inizio >= fine) return 1;             // Caso base
    if (str[inizio] != str[fine]) return 0;   // Non palindromo
    return palindromo(str, inizio + 1, fine - 1);
}

void testa(char *s) {
    int len = strlen(s);
    printf("%-12s → %s\n", s,
        palindromo(s, 0, len-1) ? "PALINDROMO" : "Non palindromo");
}

int main() {
    testa("racecar");
    testa("level");
    testa("madam");
    testa("hello");
    testa("abcba");
    testa("A");
    testa("ab");
    return 0;
}
3
Torre di Hanoi
MedioRicorsione classica

Implementa la soluzione ricorsiva alla Torre di Hanoi. La funzione hanoi(n, da, a, via) sposta n dischi dal piolo da al piolo a usando via come appoggio. Stampa ogni mossa e il numero totale di mosse.

Il numero di mosse è 2ⁿ - 1. Caso base: n=1 sposta direttamente.
C
#include <stdio.h>

int mosse = 0;

void hanoi(int n, char da, char a, char via) {
    if (n == 1) {
        printf("Sposta disco 1: %c → %c\n", da, a);
        mosse++;
        return;
    }
    hanoi(n-1, da, via, a);   // Sposta n-1 dischi su via
    printf("Sposta disco %d: %c → %c\n", n, da, a);
    mosse++;
    hanoi(n-1, via, a, da);   // Sposta n-1 dischi su a
}

int main() {
    int n;
    printf("Numero di dischi: ");
    scanf("%d", &n);

    hanoi(n, 'A', 'C', 'B');
    printf("\nTotale mosse: %d (teorico: %d)\n", mosse, (1 << n) - 1);
    return 0;
}
4
Funzione di ordinamento
DifficilePuntatori a funzione / sort

Implementa il Bubble Sort come funzione bubble_sort(arr, n, confronto) dove confronto è un puntatore a funzione. Crea due funzioni di confronto: crescente e decrescente. Poi usa la stessa funzione per ordinare un array in entrambi i modi.

C
#include <stdio.h>

int crescente(int a, int b)  { return a > b; }
int decrescente(int a, int b){ return a < b; }

void bubble_sort(int arr[], int n, int (*cmp)(int, int)) {
    for (int i = 0; i < n-1; i++) {
        for (int j = 0; j < n-i-1; j++) {
            if (cmp(arr[j], arr[j+1])) {
                int tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp;
            }
        }
    }
}

void stampa(int arr[], int n) {
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");
}

int main() {
    int a[] = {64, 25, 12, 22, 11, 90, 3};
    int n = 7;

    printf("Originale:  "); stampa(a, n);

    bubble_sort(a, n, crescente);
    printf("Crescente:  "); stampa(a, n);

    bubble_sort(a, n, decrescente);
    printf("Decrescente:"); stampa(a, n);

    return 0;
}
5
Memoization — Fibonacci efficiente
DifficileRicorsione / ottimizzazione

Implementa Fibonacci con memoization: usa un array statico per memorizzare i risultati già calcolati. Confronta il numero di chiamate con e senza memoization per n=30. La versione naive fa ~2³⁰ chiamate, quella con memo fa ~2n.

C
#include <stdio.h>
#include <string.h>

#define MAX 100
long long memo[MAX];
int chiamate_memo = 0;
int chiamate_naive = 0;

long long fib_memo(int n) {
    chiamate_memo++;
    if (n <= 1) return n;
    if (memo[n] != -1) return memo[n];   // Già calcolato!
    memo[n] = fib_memo(n-1) + fib_memo(n-2);
    return memo[n];
}

long long fib_naive(int n) {
    chiamate_naive++;
    if (n <= 1) return n;
    return fib_naive(n-1) + fib_naive(n-2);
}

int main() {
    int n = 30;

    // Memoization
    memset(memo, -1, sizeof(memo));
    long long risultato = fib_memo(n);
    printf("Fibonacci(%d) = %lld\n", n, risultato);
    printf("Chiamate con memo:  %d\n", chiamate_memo);

    // Naive
    fib_naive(n);
    printf("Chiamate naive:     %d\n", chiamate_naive);
    printf("Speedup:            %.0fx\n", (double)chiamate_naive / chiamate_memo);

    return 0;
}