Esercizi di C

In questa pagina trovi 10 esercizi di linguaggio C ordinati dal più semplice al più complesso: si parte dal classico «Ciao, mondo!» e si arriva a puntatori, memoria dinamica e liste concatenate.

Ogni esercizio contiene la consegna, un breve richiamo di teoria, la soluzione commentata e la spiegazione passo passo. Per compilare puoi usare gcc nomefile.c -o programma oppure un compilatore online.

Indice degli esercizi


Esercizio 1 – Ciao, mondo!

Livello: Base · Argomenti: struttura di un programma, printf

Consegna: Scrivi un programma che stampi a schermo la frase «Ciao, mondo!».

Teoria in breve: Ogni programma C parte dalla funzione main. Le funzioni di input/output come printf sono dichiarate nella libreria standard stdio.h, che va inclusa con #include.

Soluzione

#include <stdio.h>

int main(void) {
    printf("Ciao, mondo!\n");
    return 0;
}

Spiegazione

#include <stdio.h> dice al compilatore di usare la libreria di input/output standard.

int main(void) è il punto di ingresso: il sistema operativo esegue il programma partendo da qui. printf stampa il testo e \n manda a capo.

return 0; comunica al sistema che il programma è terminato senza errori. Per compilare ed eseguire: gcc ciao.c -o ciao e poi ./ciao.


Esercizio 2 – Somma e media di due numeri

Livello: Base · Argomenti: variabili, tipi int e float, scanf

Consegna: Chiedi all’utente due numeri interi e stampa la loro somma e la loro media (con due cifre decimali).

Teoria in breve: Una variabile è uno spazio di memoria con un nome e un tipo. int contiene numeri interi, float e double numeri con la virgola. scanf legge un valore da tastiera e lo salva all’indirizzo della variabile (per questo si usa &).

Soluzione

#include <stdio.h>

int main(void) {
    int a, b;
    printf("Inserisci il primo numero: ");
    scanf("%d", &a);
    printf("Inserisci il secondo numero: ");
    scanf("%d", &b);

    int somma = a + b;
    float media = (a + b) / 2.0f;

    printf("Somma: %d\n", somma);
    printf("Media: %.2f\n", media);
    return 0;
}

Spiegazione

%d è lo specificatore di formato per gli interi, %.2f stampa un decimale con due cifre dopo la virgola.

Attenzione alla media: (a + b) / 2 sarebbe una divisione intera (7 / 2 = 3). Dividendo per 2.0f forziamo il calcolo in virgola mobile e otteniamo 3.50.


Esercizio 3 – Pari o dispari, positivo o negativo

Livello: Base · Argomenti: if / else if / else, operatore modulo %

Consegna: Leggi un numero intero e indica se è pari o dispari, e se è positivo, negativo o zero.

Teoria in breve: L’istruzione if esegue un blocco solo se la condizione è vera; else gestisce il caso contrario. L’operatore % restituisce il resto della divisione: un numero è pari se il resto della divisione per 2 è 0.

Soluzione

#include <stdio.h>

int main(void) {
    int n;
    printf("Inserisci un numero intero: ");
    scanf("%d", &n);

    if (n % 2 == 0) {
        printf("%d e' pari\n", n);
    } else {
        printf("%d e' dispari\n", n);
    }

    if (n > 0) {
        printf("Il numero e' positivo\n");
    } else if (n < 0) {
        printf("Il numero e' negativo\n");
    } else {
        printf("Il numero e' zero\n");
    }
    return 0;
}

Spiegazione

Il primo if usa n % 2 == 0: nota il doppio uguale ==, che confronta, mentre il singolo = assegna (errore classico dei principianti!).

Il secondo blocco mostra la catena if / else if / else: le condizioni vengono controllate in ordine e viene eseguito solo il primo ramo vero.


Esercizio 4 – Calcolatrice con switch

Livello: Base · Argomenti: switch, char, double, gestione errori

Consegna: Leggi un’espressione nel formato numero operatore numero (es. 4 * 2) e stampa il risultato. Gestisci la divisione per zero e gli operatori non validi.

Teoria in breve: switch confronta una variabile con una serie di valori costanti (case). È più leggibile di tanti if quando si sceglie tra molte opzioni. Ogni case va chiuso con break, altrimenti l’esecuzione «cade» nel caso successivo.

Soluzione

#include <stdio.h>

int main(void) {
    double a, b;
    char op;
    printf("Scrivi un'operazione (es. 4 * 2): ");
    scanf("%lf %c %lf", &a, &op, &b);

    switch (op) {
        case '+': printf("Risultato: %.2f\n", a + b); break;
        case '-': printf("Risultato: %.2f\n", a - b); break;
        case '*': printf("Risultato: %.2f\n", a * b); break;
        case '/':
            if (b != 0)
                printf("Risultato: %.2f\n", a / b);
            else
                printf("Errore: divisione per zero\n");
            break;
        default:
            printf("Operatore non valido\n");
    }
    return 0;
}

Spiegazione

Con un solo scanf leggiamo tre valori: %lf per i double e %c per il carattere dell’operatore. Lo spazio prima di %c fa saltare eventuali spazi.

Il ramo default viene eseguito quando nessun case corrisponde: è il posto giusto per gestire input non validi. Nel caso '/' controlliamo prima che il divisore non sia zero.


Esercizio 5 – La tabellina con il ciclo for

Livello: Base · Argomenti: ciclo for, contatori

Consegna: Chiedi un numero all’utente e stampa la sua tabellina da 1 a 10 (es. 3 x 1 = 3 … 3 x 10 = 30).

Teoria in breve: Il ciclo for ripete un blocco un numero noto di volte. Ha tre parti: inizializzazione (int i = 1), condizione (i <= 10) e aggiornamento (i++).

Soluzione

#include <stdio.h>

int main(void) {
    int n;
    printf("Di quale numero vuoi la tabellina? ");
    scanf("%d", &n);

    for (int i = 1; i <= 10; i++) {
        printf("%d x %d = %d\n", n, i, n * i);
    }
    return 0;
}

Spiegazione

A ogni giro i vale 1, 2, 3… fino a 10; quando la condizione i <= 10 diventa falsa il ciclo termina.

Variante per esercitarti: usa due cicli for annidati per stampare l’intera tavola pitagorica 10×10.


Esercizio 6 – Indovina il numero

Livello: Intermedio · Argomenti: ciclo while, numeri casuali, contatori

Consegna: Il computer sceglie un numero casuale tra 1 e 100. L’utente prova a indovinarlo e il programma risponde «troppo alto» o «troppo basso» finché non indovina, poi stampa il numero di tentativi.

Teoria in breve: Il ciclo while si usa quando non si sa in anticipo quante ripetizioni serviranno: continua finché la condizione è vera. rand() genera numeri pseudo-casuali; srand(time(NULL)) inizializza il generatore in modo che cambi a ogni esecuzione.

Soluzione

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand(time(NULL));
    int segreto = rand() % 100 + 1;
    int tentativo = 0, tentativi = 0;

    printf("Ho pensato un numero tra 1 e 100. Indovinalo!\n");
    while (tentativo != segreto) {
        printf("Il tuo tentativo: ");
        scanf("%d", &tentativo);
        tentativi++;

        if (tentativo < segreto)
            printf("Troppo basso!\n");
        else if (tentativo > segreto)
            printf("Troppo alto!\n");
    }
    printf("Bravo! Indovinato in %d tentativi.\n", tentativi);
    return 0;
}

Spiegazione

rand() % 100 dà un valore tra 0 e 99; aggiungendo 1 otteniamo l’intervallo 1–100.

Il ciclo continua finché tentativo != segreto. La variabile tentativi è un contatore: viene incrementata a ogni giro con tentativi++.

Prova a sfruttare la ricerca binaria (parti da 50, poi 25 o 75…): indovinerai sempre in massimo 7 tentativi!


Esercizio 7 – Statistiche su un array di voti

Livello: Intermedio · Argomenti: array, #define, cicli, cast

Consegna: Leggi 5 voti, salvali in un array e stampa il voto massimo, il minimo e la media.

Teoria in breve: Un array è una sequenza di elementi dello stesso tipo memorizzati uno dopo l’altro, a cui si accede con un indice che parte da 0. La dimensione si fissa al momento della dichiarazione; con #define diamo un nome a una costante.

Soluzione

#include <stdio.h>
#define N 5

int main(void) {
    int voti[N];
    for (int i = 0; i < N; i++) {
        printf("Voto %d: ", i + 1);
        scanf("%d", &voti[i]);
    }

    int max = voti[0], min = voti[0], somma = 0;
    for (int i = 0; i < N; i++) {
        if (voti[i] > max) max = voti[i];
        if (voti[i] < min) min = voti[i];
        somma += voti[i];
    }

    printf("Massimo: %d\n", max);
    printf("Minimo: %d\n", min);
    printf("Media: %.2f\n", (float)somma / N);
    return 0;
}

Spiegazione

Il primo ciclo riempie l’array; il secondo lo scorre per calcolare i risultati. Gli indici vanno da 0 a N-1: scrivere voti[5] uscirebbe dall’array (errore grave in C, che non lo controlla per te).

max e min partono dal primo elemento e vengono aggiornati quando si trova un valore più grande o più piccolo.

(float)somma è un cast: converte la somma in decimale prima della divisione, evitando la divisione intera.


Esercizio 8 – Fattoriale con una funzione ricorsiva

Livello: Intermedio · Argomenti: funzioni, parametri, valore di ritorno, ricorsione

Consegna: Scrivi una funzione fattoriale(n) che calcoli n! = n × (n-1) × … × 1 e usala nel main. Accetta solo valori tra 0 e 20.

Teoria in breve: Una funzione è un blocco di codice riutilizzabile che riceve dei parametri e restituisce un risultato. Una funzione è ricorsiva quando richiama sé stessa: serve sempre un caso base che fermi la ricorsione.

Soluzione

#include <stdio.h>

unsigned long long fattoriale(int n) {
    if (n <= 1)
        return 1;                      /* caso base */
    return n * fattoriale(n - 1);      /* passo ricorsivo */
}

int main(void) {
    int n;
    printf("Inserisci un numero (0-20): ");
    scanf("%d", &n);

    if (n < 0 || n > 20) {
        printf("Valore fuori intervallo\n");
        return 1;
    }
    printf("%d! = %llu\n", n, fattoriale(n));
    return 0;
}

Spiegazione

Per calcolare fattoriale(4) la funzione calcola 4 * fattoriale(3), che a sua volta chiama fattoriale(2) e così via fino al caso base n <= 1, che restituisce 1.

Il fattoriale cresce velocissimo: per questo usiamo unsigned long long (stampato con %llu) e limitiamo l’input a 20, oltre il quale il risultato non entra più nella variabile (overflow).

Esercizio extra: riscrivi la funzione in versione iterativa con un ciclo for.


Esercizio 9 – Frase palindroma

Livello: Avanzato · Argomenti: stringhe, puntatori, ctype.h, fgets

Consegna: Leggi una frase e stabilisci se è palindroma, ignorando spazi, punteggiatura e maiuscole (es. «I topi non avevano nipoti»).

Teoria in breve: In C una stringa è un array di char che termina con il carattere speciale '\0'. fgets legge un’intera riga (spazi compresi) in modo sicuro; string.h e ctype.h offrono funzioni utili come strlen, isalnum e tolower.

Soluzione

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

int palindroma(const char *s) {
    int i = 0;
    int j = strlen(s) - 1;
    while (i < j) {
        if (!isalnum((unsigned char)s[i])) { i++; continue; }
        if (!isalnum((unsigned char)s[j])) { j--; continue; }
        if (tolower((unsigned char)s[i]) != tolower((unsigned char)s[j]))
            return 0;
        i++;
        j--;
    }
    return 1;
}

int main(void) {
    char frase[200];
    printf("Scrivi una frase: ");
    fgets(frase, sizeof frase, stdin);
    frase[strcspn(frase, "\n")] = '\0';

    if (palindroma(frase))
        printf("E' palindroma!\n");
    else
        printf("Non e' palindroma.\n");
    return 0;
}

Spiegazione

Usiamo la tecnica dei due indici: i parte dall’inizio, j dalla fine, e si avvicinano confrontando i caratteri. Appena due caratteri differiscono la funzione restituisce 0 (falso).

isalnum ci fa saltare spazi e punteggiatura; tolower rende il confronto indipendente dalle maiuscole.

fgets conserva il ritorno a capo finale: strcspn trova la sua posizione e lo sostituiamo con '\0'. Il parametro const char *s è un puntatore al primo carattere: const garantisce che la funzione non modifichi la stringa.


Esercizio 10 – Lista concatenata con memoria dinamica

Livello: Avanzato · Argomenti: struct, puntatori, malloc / free, typedef

Consegna: Leggi numeri finché l’utente non inserisce 0, salvandoli in una lista concatenata (inserimento in testa). Stampa la lista e libera tutta la memoria alla fine.

Teoria in breve: Una struct raggruppa più dati in un unico tipo. Una lista concatenata è una catena di nodi in cui ognuno contiene un valore e un puntatore al nodo successivo; a differenza di un array può crescere quanto serve. I nodi si creano con malloc (memoria dinamica) e vanno restituiti con free.

Soluzione

#include <stdio.h>
#include <stdlib.h>

typedef struct Nodo {
    int valore;
    struct Nodo *next;
} Nodo;

Nodo *inserisci_in_testa(Nodo *testa, int valore) {
    Nodo *nuovo = malloc(sizeof(Nodo));
    if (nuovo == NULL) {
        perror("malloc");
        exit(1);
    }
    nuovo->valore = valore;
    nuovo->next = testa;
    return nuovo;
}

void stampa(const Nodo *testa) {
    for (const Nodo *p = testa; p != NULL; p = p->next)
        printf("%d -> ", p->valore);
    printf("NULL\n");
}

void libera(Nodo *testa) {
    while (testa != NULL) {
        Nodo *tmp = testa;
        testa = testa->next;
        free(tmp);
    }
}

int main(void) {
    Nodo *lista = NULL;
    int x;
    printf("Inserisci numeri (0 per terminare):\n");
    while (scanf("%d", &x) == 1 && x != 0)
        lista = inserisci_in_testa(lista, x);

    stampa(lista);
    libera(lista);
    return 0;
}

Spiegazione

typedef ci permette di scrivere Nodo invece di struct Nodo. Ogni nodo contiene valore e next, il puntatore al nodo successivo; l’ultimo nodo punta a NULL.

inserisci_in_testa alloca un nuovo nodo, lo collega alla vecchia testa e lo restituisce come nuova testa. L’operatore -> accede ai campi di una struct tramite puntatore. Controllare che malloc non restituisca NULL è una buona pratica.

stampa scorre la lista seguendo i puntatori next. libera salva il nodo corrente in tmp prima di avanzare, poi lo libera: senza free avremmo un memory leak.

Sfida: aggiungi una funzione che inserisca in coda e una che rimuova il primo nodo con un dato valore.


Consiglio: prova sempre a risolvere l’esercizio da solo prima di leggere la soluzione, poi modificalo per sperimentare: è il modo più veloce per imparare.