Programmazione in C
Questo tutorial copre gli elementi fondamentali del linguaggio C, dalla scrittura del primo programma fino alla gestione dinamica della memoria con liste concatenate.
Prerequisiti
Section titled “Prerequisiti”- Un editor di testo (VS Code, Notepad++, ecc.)
- Un compilatore C (gcc su Linux, MinGW/gcc su Windows)
- Terminale / Prompt dei comandi
Installare il compilatore C su Windows
Section titled “Installare il compilatore C su Windows”Su Linux il compilatore gcc è spesso preinstallato (altrimenti sudo apt install gcc).
Su Windows invece va installato manualmente. La scelta più comune è MinGW-w64.
Installare MinGW-w64
Section titled “Installare MinGW-w64”-
Vai su MinGW-w64 oppure scarica direttamente MSYS2 (il metodo più semplice):
- Scarica MSYS2 da msys2.org
- Esegui l’installer e segui le istruzioni
-
Apri MSYS2 UCRT64 dal menu Start e aggiorna i pacchetti:
Terminal window pacman -Syu -
Installa il compilatore GCC e gli strumenti di build:
Terminal window pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gdb make -
Aggiungi MinGW al PATH di Windows:
- Cerca “Variabili d’ambiente” nel menu Start
- Clicca su Variabili d’ambiente…
- In Variabili di sistema, seleziona
Pathe clicca Modifica… - Aggiungi:
C:\msys64\ucrt64\bin - Clicca OK e riavvia il terminale
-
Verifica l’installazione:
Terminal window gcc --versiongdb --version
In breve: MinGW-w64 standalone (alternativa)
Section titled “In breve: MinGW-w64 standalone (alternativa)”Se non vuoi usare MSYS2, puoi scaricare il pacchetto standalone MinGW-w64 da mingw-w64.org e aggiungere bin/ al PATH.
Configurare VS Code per il C
Section titled “Configurare VS Code per il C”1. Estensioni necessarie
Section titled “1. Estensioni necessarie”Apri VS Code e installa queste estensioni:
| Estensione | Autore | Ruolo |
|---|---|---|
| C/C++ | Microsoft | IntelliSense, debug, syntax highlighting |
| Code Runner | Jun Han | Esegue velocemente il file corrente |
2. Compilare con un click — Code Runner
Section titled “2. Compilare con un click — Code Runner”Dopo aver installato Code Runner, puoi eseguire qualsiasi file .c con:
- Tasto destro → Run Code (oppure
Ctrl+Alt+N) - L’output appare nel pannello OUTPUT
Code Runner usa automaticamente gcc se presente nel PATH.
3. Configurare il debug con C/C++
Section titled “3. Configurare il debug con C/C++”Per eseguire il debug passo-passo:
- Apri la cartella del tuo progetto in VS Code
- Crea un file
programma.ccon del codice C - Vai al pannello Run and Debug (icona ▶️ a sinistra)
- Clicca create a launch.json file e seleziona C++ (GDB/LLDB)
- VS Code creerà automaticamente un
.vscode/launch.jsonsimile a:
{ "version": "0.2.0", "configurations": [ { "name": "Debug C", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/programma.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "gdb.exe", "preLaunchTask": "gcc build" } ]}- Crea anche
.vscode/tasks.jsonper compilare automaticamente prima del debug:
{ "version": "2.0.0", "tasks": [ { "type": "shell", "label": "gcc build", "command": "gcc", "args": [ "-g", "-o", "${workspaceFolder}/programma.exe", "${workspaceFolder}/programma.c" ], "group": "build", "problemMatcher": ["$gcc"] } ]}Ora premi F5 per avviare il debug: VS Code compilerà automaticamente il file e lo eseguirà con GDB.
4. Compilare manualmente dal terminale integrato
Section titled “4. Compilare manualmente dal terminale integrato”VS Code ha un terminale integrato (Ctrl+ò o View → Terminal). Puoi usarlo per compilare manualmente:
gcc -g -o programma.exe programma.c.\programma.exe1. Primo programma in C
Section titled “1. Primo programma in C”Struttura base
Section titled “Struttura base”#include <stdio.h> // Input/output library
int main() { printf("Hello, World!\n"); return 0;}Ogni programma C ha:
#include <stdio.h>— direttiva per includere la libreria standard di I/Oint main()— funzione principale, punto di ingresso del programmareturn 0;— indica che il programma è terminato correttamente
Scrittura, compilazione ed esecuzione
Section titled “Scrittura, compilazione ed esecuzione”1. Scrivi il codice in un file programma.c
2. Compila con gcc:
gcc programma.c -o programma3. Esegui:
./programmaOpzioni utili:
gcc -Wall -Wextra -o programma programma.c # Con tutti i warninggcc -g -o programma programma.c # Con info di debug (per gdb)1. Scrivi il codice in un file programma.c
2. Compila con gcc (MinGW):
gcc programma.c -o programma.exe3. Esegui:
programma.exeOppure usa Visual Studio e il compilatore MSVC:
cl programma.cprogramma.exe2. Variabili e tipi di dato
Section titled “2. Variabili e tipi di dato”int main() { // Tipi di base int a = 10; // Intero (32 bit) float b = 3.14f; // Virgola mobile (32 bit) double c = 3.1415926535; // Virgola mobile (64 bit) char d = 'A'; // Carattere (8 bit)
// Modificatori unsigned int e = 42; // Solo valori positivi long int f = 100000L; // Intero lungo short int g = 5; // Intero corto
// Costante const int MAX = 100;
printf("int: %d, float: %.2f, double: %.2f, char: %c\n", a, b, c, d); return 0;}Specificatori di formato printf
Section titled “Specificatori di formato printf”| Specificatore | Tipo |
|---|---|
%d / %i | int |
%f | float / double |
%c | char |
%s | stringa (char*) |
%p | puntatore |
%x | esadecimale |
%lu | unsigned long |
3. Array e matrici
Section titled “3. Array e matrici”Array monodimensionali
Section titled “Array monodimensionali”#include <stdio.h>
int main() { // Dichiarazione e inizializzazione int numeri[5] = {10, 20, 30, 40, 50};
// Oppure senza specificare la dimensione int voti[] = {8, 7, 9, 6, 10};
// Accesso agli elementi for (int i = 0; i < 5; i++) { printf("numeri[%d] = %d\n", i, numeri[i]); }
// Calcolare la dimensione di un array int size = sizeof(voti) / sizeof(voti[0]); printf("L'array ha %d elementi\n", size);
return 0;}Matrici (array bidimensionali)
Section titled “Matrici (array bidimensionali)”#include <stdio.h>
int main() { // Matrice 3x3 int matrice[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
// Stampa a matrice for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { printf("%d ", matrice[i][j]); } printf("\n"); }
return 0;}4. Funzioni e passaggio di parametri
Section titled “4. Funzioni e passaggio di parametri”Funzioni semplici
Section titled “Funzioni semplici”#include <stdio.h>
// Prototipi (dichiarazione)int somma(int a, int b);void stampaMessaggio(char messaggio[]);
int main() { int risultato = somma(5, 3); printf("Risultato: %d\n", risultato);
stampaMessaggio("Ciao mondo!"); return 0;}
// Definizioneint somma(int a, int b) { return a + b;}
void stampaMessaggio(char messaggio[]) { printf("%s\n", messaggio);}Passaggio per valore vs per riferimento
Section titled “Passaggio per valore vs per riferimento”#include <stdio.h>
// Passaggio per valore (copia)void raddoppiaValore(int x) { x = x * 2; // Modifica solo la copia locale}
// Passaggio per riferimento (puntatore)void raddoppiaRiferimento(int *x) { *x = *x * 2; // Modifica il valore originale}
int main() { int n = 5;
raddoppiaValore(n); printf("Dopo valore: %d\n", n); // 5 (invariato)
raddoppiaRiferimento(&n); printf("Dopo riferimento: %d\n", n); // 10 (modificato)
return 0;}Array come parametri di funzione
Section titled “Array come parametri di funzione”#include <stdio.h>
// Un array decade sempre a puntatorevoid stampaArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n");}
void modificaArray(int *arr, int size) { for (int i = 0; i < size; i++) { arr[i] *= 2; // Modifica l'array originale }}
int main() { int numeri[] = {1, 2, 3, 4, 5}; int size = sizeof(numeri) / sizeof(numeri[0]);
stampaArray(numeri, size); modificaArray(numeri, size); stampaArray(numeri, size); // {2, 4, 6, 8, 10}
return 0;}5. Puntatori
Section titled “5. Puntatori”Concetti base
Section titled “Concetti base”#include <stdio.h>
int main() { int x = 42; int *ptr; // Dichiarazione puntatore a int
ptr = &x; // ptr contiene l'indirizzo di x
printf("Valore di x: %d\n", x); printf("Indirizzo di x: %p\n", (void*)&x); printf("Valore di ptr: %p\n", (void*)ptr); printf("Dereferenza di ptr: %d\n", *ptr);
// Modifica tramite puntatore *ptr = 100; printf("Nuovo valore di x: %d\n", x); // 100
return 0;}Puntatori ad array
Section titled “Puntatori ad array”#include <stdio.h>
int main() { int arr[] = {10, 20, 30, 40, 50}; int *p = arr; // Equivalenti: p = &arr[0]
// Aritmetica dei puntatori printf("Primo elemento: %d\n", *p); // 10 printf("Secondo elemento: %d\n", *(p+1)); // 20 printf("Terzo elemento: %d\n", *(p+2)); // 30
// Scorrere un array con puntatori for (int *q = arr; q < arr + 5; q++) { printf("%d ", *q); } printf("\n");
return 0;}Puntatori a funzioni
Section titled “Puntatori a funzioni”#include <stdio.h>
int somma(int a, int b) { return a + b; }int differenza(int a, int b) { return a - b; }int prodotto(int a, int b) { return a * b; }
int main() { // Dichiarazione puntatore a funzione int (*operazione)(int, int);
operazione = somma; printf("Somma: %d\n", operazione(10, 5));
operazione = differenza; printf("Differenza: %d\n", operazione(10, 5));
operazione = prodotto; printf("Prodotto: %d\n", operazione(10, 5));
return 0;}6. Gestione delle stringhe
Section titled “6. Gestione delle stringhe”In C le stringhe sono array di char terminati da \0 (null terminator).
#include <stdio.h>#include <string.h> // Per funzioni sulle stringhe
int main() { // Dichiarazione char s1[] = "Ciao"; // Array automatico char s2[20] = "Mondo"; // Con dimensione esplicita char *s3 = "Hello"; // Stringa constante (non modificabile!)
// Copia e concatenazione char buffer[50]; strcpy(buffer, s1); // Copia "Ciao" in buffer strcat(buffer, " "); // Concatena spazio strcat(buffer, s2); // Concatena "Mondo" printf("%s\n", buffer); // "Ciao Mondo"
// Lunghezza printf("Lunghezza: %zu\n", strlen(buffer));
// Confronto if (strcmp(s1, "Ciao") == 0) { printf("Le stringhe sono uguali\n"); }
// Input da tastiera char nome[50]; printf("Inserisci il nome: "); fgets(nome, sizeof(nome), stdin); nome[strcspn(nome, "\n")] = '\0'; // Rimuove newline
printf("Ciao, %s!\n", nome);
return 0;}Principali funzioni di <string.h>
Section titled “Principali funzioni di <string.h>”| Funzione | Descrizione |
|---|---|
strlen(s) | Restituisce la lunghezza (escluso il carattere NUL) |
strcpy(dest, src) | Copia src in dest |
strcat(dest, src) | Concatena src in fondo a dest |
strcmp(s1, s2) | Confronta: 0 = uguali, <0 se s1 < s2, >0 se s1 > s2 |
strchr(s, c) | Trova la prima occorrenza di c in s |
strstr(s1, s2) | Trova la prima occorrenza di s2 in s1 |
7. Strutture (struct)
Section titled “7. Strutture (struct)”#include <stdio.h>#include <string.h>
// Definizione di una strutturastruct Studente { char nome[50]; char cognome[50]; int matricola; float media;};
int main() { // Inizializzazione struct Studente s1 = {"Mario", "Rossi", 12345, 27.5};
// Accesso ai campi printf("Nome: %s\n", s1.nome); printf("Cognome: %s\n", s1.cognome); printf("Matricola: %d\n", s1.matricola); printf("Media: %.2f\n", s1.media);
// Modifica s1.media = 28.0;
// Array di strutture struct Studente classe[3] = { {"Mario", "Rossi", 12345, 27.5}, {"Anna", "Bianchi", 12346, 30.0}, {"Luca", "Verdi", 12347, 24.0} };
for (int i = 0; i < 3; i++) { printf("%s %s: %.2f\n", classe[i].nome, classe[i].cognome, classe[i].media); }
return 0;}Struct e puntatori
Section titled “Struct e puntatori”#include <stdio.h>#include <string.h>
struct Punto { int x; int y;};
void sposta(struct Punto *p, int dx, int dy) { p->x += dx; // Equivalente a (*p).x += dx p->y += dy;}
int main() { struct Punto p1 = {10, 20}; struct Punto *ptr = &p1;
printf("(%d, %d)\n", ptr->x, ptr->y);
sposta(ptr, 5, -3); printf("Dopo spostamento: (%d, %d)\n", p1.x, p1.y);
return 0;}typedef
Section titled “typedef”#include <stdio.h>
// typedef crea un alias per il tipotypedef struct { char nome[50]; int eta;} Persona;
int main() { Persona p1 = {"Alice", 25}; // Non serve più "struct" printf("%s ha %d anni\n", p1.nome, p1.eta);
return 0;}8. Gestione dinamica della memoria
Section titled “8. Gestione dinamica della memoria”Le funzioni di allocazione dinamica sono definite in <stdlib.h>.
| Funzione | Descrizione |
|---|---|
malloc(size) | Alloca size byte, restituisce un puntatore |
calloc(n, size) | Alloca n elementi di size byte, inizializzati a 0 |
realloc(ptr, new_size) | Ridimensiona un blocco allocato |
free(ptr) | Libera la memoria allocata |
Esempio: array dinamico
Section titled “Esempio: array dinamico”#include <stdio.h>#include <stdlib.h>
int main() { int n;
printf("Quanti numeri vuoi inserire? "); scanf("%d", &n);
// Allocazione dinamica di un array int *numeri = (int*)malloc(n * sizeof(int));
// Controllo se l'allocazione è riuscita if (numeri == NULL) { printf("Errore di allocazione!\n"); return 1; }
// Inserimento dati for (int i = 0; i < n; i++) { printf("Inserisci numero %d: ", i + 1); scanf("%d", &numeri[i]); }
// Stampa printf("Numeri inseriti: "); for (int i = 0; i < n; i++) { printf("%d ", numeri[i]); } printf("\n");
// Liberare la memoria free(numeri);
return 0;}Usare calloc vs malloc
Section titled “Usare calloc vs malloc”// malloc: memoria non inizializzata (può contenere "spazzatura")int *arr1 = (int*)malloc(5 * sizeof(int));
// calloc: memoria inizializzata a zeroint *arr2 = (int*)calloc(5, sizeof(int));
if (arr2 != NULL) { for (int i = 0; i < 5; i++) { printf("%d ", arr2[i]); // 0 0 0 0 0 } free(arr2);}realloc: ridimensionare un array
Section titled “realloc: ridimensionare un array”#include <stdio.h>#include <stdlib.h>
int main() { int *numeri = (int*)malloc(3 * sizeof(int)); numeri[0] = 10; numeri[1] = 20; numeri[2] = 30;
// Riallocazione per array più grande int *temp = (int*)realloc(numeri, 5 * sizeof(int)); if (temp == NULL) { printf("Errore realloc!\n"); free(numeri); return 1; } numeri = temp;
numeri[3] = 40; numeri[4] = 50;
for (int i = 0; i < 5; i++) { printf("%d ", numeri[i]); } printf("\n");
free(numeri); return 0;}9. Liste concatenate
Section titled “9. Liste concatenate”Una lista concatenata è una struttura dati dinamica composta da nodi collegati tramite puntatori.
Definizione del nodo
Section titled “Definizione del nodo”typedef struct Nodo { int dato; struct Nodo *next; // Puntatore al nodo successivo} Nodo;Lista concatenata completa
Section titled “Lista concatenata completa”#include <stdio.h>#include <stdlib.h>
typedef struct Nodo { int dato; struct Nodo *next;} Nodo;
// Inserimento in testaNodo* inserisciTesta(Nodo *testa, int valore) { Nodo *nuovo = (Nodo*)malloc(sizeof(Nodo)); if (nuovo == NULL) { printf("Errore allocazione!\n"); return testa; } nuovo->dato = valore; nuovo->next = testa; return nuovo;}
// Inserimento in codaNodo* inserisciCoda(Nodo *testa, int valore) { Nodo *nuovo = (Nodo*)malloc(sizeof(Nodo)); if (nuovo == NULL) { printf("Errore allocazione!\n"); return testa; } nuovo->dato = valore; nuovo->next = NULL;
if (testa == NULL) { return nuovo; // Lista vuota }
Nodo *corrente = testa; while (corrente->next != NULL) { corrente = corrente->next; } corrente->next = nuovo; return testa;}
// Rimuovi un valoreNodo* rimuovi(Nodo *testa, int valore) { Nodo *corrente = testa; Nodo *precedente = NULL;
while (corrente != NULL && corrente->dato != valore) { precedente = corrente; corrente = corrente->next; }
if (corrente == NULL) return testa; // Non trovato
if (precedente == NULL) { testa = corrente->next; // Rimozione in testa } else { precedente->next = corrente->next; }
free(corrente); return testa;}
// Stampa la listavoid stampaLista(Nodo *testa) { Nodo *corrente = testa; while (corrente != NULL) { printf("%d -> ", corrente->dato); corrente = corrente->next; } printf("NULL\n");}
// Libera tutta la memoriavoid liberaLista(Nodo *testa) { Nodo *corrente = testa; while (corrente != NULL) { Nodo *prossimo = corrente->next; free(corrente); corrente = prossimo; }}
// Cerca un elementoint cerca(Nodo *testa, int valore) { Nodo *corrente = testa; int pos = 0; while (corrente != NULL) { if (corrente->dato == valore) return pos; corrente = corrente->next; pos++; } return -1; // Non trovato}
int main() { Nodo *lista = NULL;
lista = inserisciTesta(lista, 30); lista = inserisciTesta(lista, 20); lista = inserisciTesta(lista, 10); stampaLista(lista); // 10 -> 20 -> 30 -> NULL
lista = inserisciCoda(lista, 40); lista = inserisciCoda(lista, 50); stampaLista(lista); // 10 -> 20 -> 30 -> 40 -> 50 -> NULL
lista = rimuovi(lista, 30); stampaLista(lista); // 10 -> 20 -> 40 -> 50 -> NULL
int pos = cerca(lista, 40); printf("Valore 40 trovato alla posizione: %d\n", pos);
liberaLista(lista); return 0;}10. Debug di un programma C
Section titled “10. Debug di un programma C”Compilare con simboli di debug
Section titled “Compilare con simboli di debug”gcc -g -o programma programma.cUsare GDB (Linux / MinGW)
Section titled “Usare GDB (Linux / MinGW)”gdb ./programmaComandi GDB essenziali:
| Comando | Descrizione |
|---|---|
break main | Imposta breakpoint su main |
break 10 | Breakpoint alla riga 10 |
run | Avvia il programma |
next (o n) | Esegue riga successiva (senza entrare nelle funzioni) |
step (o s) | Entra nella funzione chiamata |
print x | Stampa il valore di x |
continue (o c) | Continua fino al prossimo breakpoint |
quit | Esci da gdb |
Usare Valgrind (Linux) per memory leak
Section titled “Usare Valgrind (Linux) per memory leak”gcc -g -o programma programma.cvalgrind --leak-check=full ./programmaDebug su Windows con Visual Studio
Section titled “Debug su Windows con Visual Studio”- Apri il file
.cin Visual Studio - Premi
F9per impostare un breakpoint - Premi
F5per avviare il debug - Usa
F10per eseguire riga dopo riga - Usa
F11per entrare nelle funzioni
Riepilogo — schema concettuale
Section titled “Riepilogo — schema concettuale”┌─────────────────────────────────────────┐│ PROGRAMMA C │├─────────────────────────────────────────┤│ #include <stdio.h> ││ ││ int main() { ││ ┌─ Variabili e tipi ││ ├─ Array e matrici ││ ├─ Puntatori ││ ├─ Stringhe (char[]) ││ ├─ Strutture (struct) ││ ├─ Funzioni (parametri, return) ││ └─ Memoria dinamica (malloc/free) ││ } │├─────────────────────────────────────────┤│ Compilazione: gcc -o prog prog.c ││ Esecuzione: ./prog (Linux) ││ prog.exe (Windows) ││ Debug: gdb ./prog ││ Memory check: valgrind ./prog │└─────────────────────────────────────────┘Esercizi proposti
Section titled “Esercizi proposti”-
Array e funzioni: Scrivi una funzione che accetta un array e la sua dimensione e restituisce il valore massimo.
-
Stringhe: Scrivi una funzione che conta quante vocali ci sono in una stringa.
-
Strutture: Definisci una struttura
Rettangolocon base e altezza, poi scrivi una funzione che calcola l’area. -
Memoria dinamica: Scrivi un programma che chiede all’utente quanti numeri vuole inserire, li legge, li memorizza in un array allocato dinamicamente, e poi li stampa in ordine inverso.
-
Lista concatenata: Aggiungi alla lista dell’esempio una funzione che conta il numero di elementi.