Manuale completo di C++
Dal primo hello world alle strutture dati avanzate: guida a 3 livelli con centinaia di esempi, 60 esercizi e un progetto finale.
0Cos'è C++ Base
Il C++ è un linguaggio di programmazione imperativo, procedurale e compilato, creato da Bjarne Stroustrup nel 1985 come estensione del C. È il linguaggio con cui sono stati scritti sistemi operativi, browser, videogiochi AAA e gran parte dei software ad alte prestazioni.
Perché imparare C++
- Vicino alla macchina: gestisci direttamente memoria e bit
- Veloce: codice compilato in linguaggio macchina, senza runtime pesante
- Multi-paradigma: procedurale, orientato agli oggetti, generico, funzionale
- Standardizzato: ISO/IEC, aggiornato ogni 3 anni (C++11, 14, 17, 20, 23)
- Ovunque: sistemi embedded, giochi, HPC, finanza, AI
Il primo programma: Hello World
// Il primo programma in C++ #include <iostream> // libreria input/output int main() { std::cout << "Ciao, mondo!" << std::endl; return 0; // 0 = successo }
Il file .cpp va compilato. Con GCC: g++ hello.cpp -o hello e poi ./hello. Con Visual Studio: premi F5. Il compilatore traduce il codice in linguaggio macchina.
1Struttura di un programma Base
Ogni programma C++ ha una struttura ben precisa: direttive al preprocessore, dichiarazioni, e la funzione main() che è il punto di ingresso.
1.1 Direttive del preprocessore
Iniziano con #. Il preprocessore le elabora prima della compilazione vera e propria.
#include <iostream> // include libreria standard #include "miofile.h" // include file locale #define PI 3.14159 // macro (sconsigliato in C++ moderno) #define MAX(a,b) ((a)>(b)?(a):(b))
1.2 Namespace
Un namespace raggruppa nomi per evitare conflitti. La libreria standard vive in std.
// Modo esplicito (consigliato) std::cout << "Ciao"; // Oppure con using (accettabile in file .cpp piccoli) using namespace std; cout << "Ciao"; // Oppure selettivo (ideale) using std::cout; using std::endl;
1.3 Funzione main
Ogni programma C++ deve avere una e una sola funzione main(). È il punto di partenza.
int main() { // codice qui return 0; // 0 = successo, altro = errore } // Con argomenti da riga di comando int main(int argc, char* argv[]) { // argc = numero argomenti // argv = array di stringhe return 0; }
1.4 Commenti
// Commento su una riga /* Commento su più righe */ /** Documentazione Doxygen * @param x valore di input * @return il doppio di x */ int doppio(int x) { return x * 2; }
2Variabili e tipi di dati Base
Una variabile è uno spazio in memoria con un nome, che contiene un valore di un certo tipo. Il tipo dice al compilatore quanto spazio riservare e come interpretare i bit.
2.1 Tipi fondamentali
| Tipo | Dimensione tipica | Range | Esempio |
|---|---|---|---|
bool | 1 byte | true / false | bool ok = true; |
char | 1 byte | -128 a 127 | char c = 'A'; |
int | 4 byte | ±2 miliardi | int n = 42; |
long | 4-8 byte | dipende dal sistema | long x = 100000L; |
long long | 8 byte | ±9×10¹⁸ | long long big = 9999999999LL; |
float | 4 byte | ~7 cifre decimali | float f = 3.14f; |
double | 8 byte | ~15 cifre decimali | double d = 3.14159; |
unsigned int | 4 byte | 0 a 4 miliardi | unsigned int u = 100; |
2.2 Dichiarazione e inizializzazione
// Dichiarazione (senza valore: contiene spazzatura!) int a; // Inizializzazione con = int b = 10; // Inizializzazione con {} (C++11, consigliata) int c{20}; // Inizializzazione con () int d(30); // Multipla int x = 1, y = 2, z = 3; // Costanti const int MAX = 100; // MAX = 200; // ERRORE: non modificabile // constexpr: valutata a compile-time (C++11) constexpr double PI = 3.14159;
In C++, una variabile locale non inizializzata contiene spazzatura (bit casuali). Usarla è un bug sottile. Inizializza sempre le tue variabili!
2.3 auto: deduzione del tipo
Dal C++11, auto fa dedurre il tipo al compilatore dal valore assegnato.
auto n = 42; // int auto pi = 3.14; // double auto c = 'A'; // char auto ok = true; // bool auto nome = std::string("Marco"); // std::string
Utile con tipi lunghi (es. iteratori) o quando il tipo è ovvio dal contesto. Non abusarne: rende il codice meno leggibile se il tipo non è chiaro.
2.4 Casting tra tipi
int i = 42; double d = i; // conversione implicita: 42.0 int j = (int)3.99; // cast stile C: j = 3 int k = static_cast<int>(3.99); // cast C++ (consigliato): k = 3 double x = static_cast<double>(7) / 2; // 3.5
7 / 2 con due int dà 3, non 3.5. Per avere un risultato decimale, almeno uno dei due operandi deve essere double o float.
3Input/Output Base
In C++ l'I/O avviene tramite stream: flussi di dati. cin è lo standard input (tastiera), cout lo standard output (schermo), cerr per gli errori.
3.1 Output con cout
#include <iostream> using namespace std; int main() { cout << "Ciao" << endl; // Ciao + a capo cout << "Ciao\n"; // stessa cosa cout << "Ho " << 17 << " anni" << endl; cout << "Pi greco = " << 3.14159 << endl; return 0; }
3.2 Input con cin
#include <iostream> using namespace std; int main() { int eta; double altezza; char iniziale; cout << "Quanti anni hai? "; cin >> eta; cout << "Quanto sei alto (m)? "; cin >> altezza; cout << "Iniziale del nome? "; cin >> iniziale; cout << "\nCiao " << iniziale << ", hai " << eta << " anni e sei alto " << altezza << " m" << endl; return 0; }
3.3 Leggere una riga intera
cin >> si ferma al primo spazio. Per leggere una riga intera (con spazi), usa getline().
#include <iostream> #include <string> using namespace std; int main() { string nomeCompleto; cout << "Come ti chiami (nome e cognome)? "; getline(cin, nomeCompleto); cout << "Ciao, " << nomeCompleto << "!" << endl; return 0; }
Se usi cin >> prima di getline(), il carattere di newline rimasto nel buffer può far saltare la lettura. Soluzione: chiama cin.ignore() prima di getline().
3.4 Formattazione dell'output
#include <iostream> #include <iomanip> // per setw, setprecision using namespace std; int main() { double pi = 3.14159265358979; cout << setprecision(4) << pi << endl; // 3.142 // Allineamento in colonna con setw cout << setw(10) << "Nome" << setw(10) << "Età" << endl; cout << setw(10) << "Marco" << setw(10) << 17 << endl; cout << setw(10) << "Sofia" << setw(10) << 16 << endl; return 0; }
4Operatori Base
4.1 Operatori aritmetici
| Operatore | Significato | Esempio | Risultato |
|---|---|---|---|
+ | Addizione | 7 + 3 | 10 |
- | Sottrazione | 7 - 3 | 4 |
* | Moltiplicazione | 7 * 3 | 21 |
/ | Divisione | 7 / 3 (int) | 2 (divisione intera!) |
/ | Divisione | 7.0 / 3 | 2.3333… |
% | Modulo (resto) | 7 % 3 | 1 |
++ | Incremento | x++ | x = x + 1 |
-- | Decremento | x-- | x = x - 1 |
7 / 2 con due int dà 3, non 3.5. Il risultato viene troncato. Per ottenere un decimale, almeno uno dei due operandi deve essere double o float: 7.0 / 2 → 3.5.
4.2 Operatori di confronto
| Operatore | Significato | Esempio |
|---|---|---|
== | Uguale | x == y |
!= | Diverso | x != y |
> | Maggiore | x > y |
< | Minore | x < y |
>= | Maggiore o uguale | x >= y |
<= | Minore o uguale | x <= y |
= è assegnazione, == è confronto. if (x = 5) assegna 5 a x e restituisce 5 (sempre vero). Il compilatore moderno avvisa, ma è un errore subdolo.
4.3 Operatori logici
| Operatore | Nome | Esempio | Risultato |
|---|---|---|---|
&& | AND logico | true && false | false |
|| | OR logico | true || false | true |
! | NOT logico | !true | false |
Con short-circuit: se il primo operando di && è false, il secondo non viene valutato. Utile per evitare errori.
int x = 5; if (x > 0 && x < 10) { cout << "x è una cifra" << endl; } // Short-circuit: se ptr è nullptr, non valuta ptr->valore if (ptr != nullptr && ptr->valore > 0) { // ... }
4.4 Operatori di assegnazione composti
int x = 10; x += 5; // x = x + 5 → 15 x -= 3; // x = x - 3 → 12 x *= 2; // x = x * 2 → 24 x /= 4; // x = x / 4 → 6 x %= 4; // x = x % 4 → 2
4.5 Precedenza degli operatori
Ordine di priorità (dal più alto al più basso) — versione semplificata:
()— parentesi!,++,--— unari*,/,%— moltiplicativi+,-— additivi<,>,<=,>=— confronto==,!=— uguaglianza&&— AND logico||— OR logico=,+=, … — assegnazione
Quando hai dubbi sulla precedenza, usa le parentesi. Rende il codice più leggibile e non dipende dalla memoria.
5Condizioni Base
5.1 if / else
int voto = 7; if (voto >= 6) { cout << "Promosso" << endl; } else { cout << "Bocciato" << endl; } // if annidati if (voto >= 9) { cout << "Eccellente" << endl; } else if (voto >= 7) { cout << "Buono" << endl; } else if (voto >= 6) { cout << "Sufficiente" << endl; } else { cout << "Insufficiente" << endl; }
5.2 Operatore ternario
// condizione ? valore_se_vero : valore_se_falso int a = 10, b = 20; int max = (a > b) ? a : b; // 20 int voto = 5; string esito = (voto >= 6) ? "Promosso" : "Bocciato";
5.3 switch / case
Utile quando hai molte alternative basate sullo stesso valore. Molto più leggibile di tanti else if.
char scelta = 'B'; switch (scelta) { case 'A': cout << "Hai scelto A" << endl; break; // senza break, "cade" nel case successivo case 'B': case 'C': cout << "Hai scelto B o C" << endl; break; default: cout << "Scelta non valida" << endl; }
Senza break, l'esecuzione "cade" nel case successivo. A volte è voluto (case raggruppati), ma spesso è un bug.
6Cicli Base
6.1 Ciclo for
Il ciclo più usato. Sintassi: for (inizializzazione; condizione; passo).
// Stampa 0, 1, 2, 3, 4 for (int i = 0; i < 5; i++) { cout << i << " "; } cout << endl; // Con passo diverso for (int i = 0; i <= 100; i += 10) { cout << i << " "; // 0 10 20 30 ... 100 } // Conteggio alla rovescia for (int i = 10; i > 0; i--) { cout << i << " "; }
6.2 Ciclo while
Ripete il blocco finché la condizione è vera. La condizione è verificata prima di ogni iterazione.
int i = 0; while (i < 5) { cout << i << " "; i++; } // Output: 0 1 2 3 4 // Legge numeri finché non viene inserito 0 int n; while (true) { cout << "Numero (0 per uscire): "; cin >> n; if (n == 0) break; cout << "Hai inserito: " << n << endl; }
6.3 Ciclo do-while
Come while, ma la condizione è verificata dopo il corpo. Il blocco viene eseguito almeno una volta.
int scelta; do { cout << "1) Gioca 2) Esci: "; cin >> scelta; if (scelta == 1) cout << "Stai giocando..." << endl; } while (scelta != 2); cout << "Arrivederci!" << endl;
6.4 Ciclo for-each (C++11)
Scorri tutti gli elementi di una collezione senza contatori. Molto più leggibile.
int voti[] = {6, 7, 8, 5, 9}; // for-each su array for (int v : voti) { cout << v << " "; // 6 7 8 5 9 } // Con auto e modifica for (auto& v : voti) { v += 1; // modifica l'array originale }
6.5 break e continue
break: esce immediatamente dal ciclocontinue: salta al passo successivo del ciclo
// Stampa solo i numeri dispari da 1 a 10 for (int i = 1; i <= 10; i++) { if (i % 2 == 0) continue; // salta i pari cout << i << " "; } // Esce quando trova il numero 5 for (int i = 1; i <= 10; i++) { if (i == 5) break; cout << i << " "; // 1 2 3 4 }
6.6 Cicli annidati
// Tabellina pitagorica 5x5 for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { cout << setw(4) << (i * j); } cout << endl; }
7Array Base
Un array è una collezione di elementi dello stesso tipo, memorizzati in posizioni contigue di memoria. Si accede tramite un indice che parte da 0.
7.1 Dichiarazione e inizializzazione
// Array di 5 interi, non inizializzati int voti[5]; // Inizializzazione completa int a[5] = {10, 20, 30, 40, 50}; // Inizializzazione parziale (resto a 0) int b[5] = {1, 2}; // {1, 2, 0, 0, 0} // Tutti a zero int c[5] = {0}; // Dimensione dedotta dal compilatore int d[] = {1, 2, 3}; // dimensione 3 // std::array (C++11, dimensione nota a compile-time) #include <array> std::array<int, 5> arr = {1, 2, 3, 4, 5};
7.2 Accesso agli elementi
int a[5] = {10, 20, 30, 40, 50}; cout << a[0] << endl; // 10 (primo elemento) cout << a[4] << endl; // 50 (ultimo elemento) a[2] = 99; // modifica il terzo elemento // Iterazione con for classico for (int i = 0; i < 5; i++) { cout << a[i] << " "; } // Iterazione con for-each for (int x : a) { cout << x << " "; }
C++ non controlla se l'indice è fuori dai limiti. a[10] su un array di 5 elementi è undefined behavior (potrebbe crashare, o peggio, corrompere memoria). Il programmatore è responsabile.
7.3 Array multidimensionali
// Matrice 3x4 int m[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; // Stampa la matrice for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { cout << setw(4) << m[i][j]; } cout << endl; }
7.4 Algoritmi comuni sugli array
int a[] = {4, 7, 1, 9, 3, 5}; int n = sizeof(a) / sizeof(a[0]); // 6 // Somma int somma = 0; for (int x : a) somma += x; cout << "Somma: " << somma << endl; // 29 // Media double media = static_cast<double>(somma) / n; cout << "Media: " << media << endl; // Massimo e minimo int max = a[0], min = a[0]; for (int i = 1; i < n; i++) { if (a[i] > max) max = a[i]; if (a[i] < min) min = a[i]; } // Ricerca lineare int cercato = 9; int pos = -1; for (int i = 0; i < n; i++) { if (a[i] == cercato) { pos = i; break; } } if (pos != -1) cout << "Trovato in posizione " << pos << endl; // Ricerca del valore massimo e sua posizione int posMax = 0; for (int i = 1; i < n; i++) { if (a[i] > a[posMax]) posMax = i; } // Inversione (reverse) in-place for (int i = 0; i < n / 2; i++) { int temp = a[i]; a[i] = a[n - 1 - i]; a[n - 1 - i] = temp; } // Ordinamento a bolle (bubble sort) for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (a[j] > a[j + 1]) { int temp = a[j]; a[j] = a[j + 1]; a[j + 1] = temp; } } }
★20 esercizi · Livello Base
Prova prima da solo, poi apri la soluzione.
Hello World
Scrivi un programma che stampa "Ciao, mondo!".
Soluzione
#include <iostream> int main() { std::cout << "Ciao, mondo!" << std::endl; return 0; }
Somma di due numeri
Leggi due interi e stampa la somma.
Soluzione
int a, b; std::cin >> a >> b; std::cout << a + b << std::endl;
Area del cerchio
Leggi il raggio e calcola l'area (π = 3.14159).
Soluzione
double r; std::cin >> r; double area = 3.14159 * r * r; std::cout << "Area: " << area << std::endl;
Pari o dispari
Leggi un intero e di se è pari o dispari.
Soluzione
int n; std::cin >> n; if (n % 2 == 0) std::cout << "Pari"; else std::cout << "Dispari";
Maggiore di tre
Leggi tre numeri e stampa il maggiore.
Soluzione
int a, b, c; std::cin >> a >> b >> c; int max = a; if (b > max) max = b; if (c > max) max = c; std::cout << max;
Tabellina del 7
Stampa la tabellina del 7.
Soluzione
for (int i = 1; i <= 10; i++) std::cout << "7 x " << i << " = " << 7*i << std::endl;
Somma 1..N
Leggi N e somma i numeri da 1 a N.
Soluzione
int n, s = 0; std::cin >> n; for (int i = 1; i <= n; i++) s += i; std::cout << s;
Fattoriale
Calcola N! con un ciclo.
Soluzione
int n; std::cin >> n; long long f = 1; for (int i = 2; i <= n; i++) f *= i; std::cout << f;
Numeri primi
Stampa i primi N numeri primi.
Soluzione
int n, cont = 0, num = 2; std::cin >> n; while (cont < n) { bool primo = true; for (int i = 2; i*i <= num; i++) if (num % i == 0) { primo = false; break; } if (primo) { std::cout << num << " "; cont++; } num++; }
Fibonacci
Stampa i primi N termini di Fibonacci.
Soluzione
int n; std::cin >> n; long long a = 0, b = 1; for (int i = 0; i < n; i++) { std::cout << a << " "; long long t = a + b; a = b; b = t; }
Inversione di cifre
Leggi un intero e stampa le cifre invertite.
Soluzione
int n; std::cin >> n; while (n > 0) { std::cout << n % 10; n /= 10; }
Palindromo
Verifica se una parola è palindroma.
Soluzione
#include <string> std::string s; std::cin >> s; bool pal = true; for (int i = 0; i < s.size()/2; i++) if (s[i] != s[s.size()-1-i]) pal = false; std::cout << (pal ? "Sì" : "No");
Media di un array
Calcola la media di N numeri letti da input.
Soluzione
int n; std::cin >> n; double s = 0; for (int i = 0; i < n; i++) { double x; std::cin >> x; s += x; } std::cout << s/n;
Massimo in un array
Trova il massimo di N numeri.
Soluzione
int n; std::cin >> n; int max; std::cin >> max; for (int i = 1; i < n; i++) { int x; std::cin >> x; if (x > max) max = x; } std::cout << max;
Conta vocali
Conta le vocali in una stringa.
Soluzione
#include <string> std::string s; std::getline(std::cin, s); int cont = 0; for (char c : s) { c = std::tolower(c); if (c=='a'||c=='e'||c=='i'||c=='o'||c=='u') cont++; } std::cout << cont;
Indovina il numero
Il programma pensa un numero (es. 42), l'utente deve indovinarlo.
Soluzione
int segreto = 42, tentativo; do { std::cin >> tentativo; if (tentativo < segreto) std::cout << "Troppo basso\n"; else if (tentativo > segreto) std::cout << "Troppo alto\n"; } while (tentativo != segreto); std::cout << "Indovinato!";
Conta cifre
Conta quante cifre ha un numero.
Soluzione
int n; std::cin >> n; int c = 0; do { n /= 10; c++; } while (n > 0); std::cout << c;
Ordinamento a bolle
Ordina un array di N interi con bubble sort.
Soluzione
int n; std::cin >> n; int a[100]; for (int i=0;i<n;i++) std::cin >> a[i]; for (int i=0;i<n-1;i++) for (int j=0;j<n-1-i;j++) if (a[j]>a[j+1]) std::swap(a[j],a[j+1]); for (int i=0;i<n;i++) std::cout << a[i] << " ";
Somma di una matrice
Calcola la somma di tutti gli elementi di una matrice 3x3.
Soluzione
int m[3][3], s = 0; for (int i=0;i<3;i++) for (int j=0;j<3;j++) { std::cin >> m[i][j]; s += m[i][j]; } std::cout << s;
Disegna un triangolo
Leggi N e disegna un triangolo di asterischi alto N.
Soluzione
int n; std::cin >> n; for (int i = 1; i <= n; i++) { for (int j = 0; j < i; j++) std::cout << "*"; std::cout << "\n"; }
8Stringhe Intermedio
In C++ ci sono due modi per gestire le stringhe: le stringhe C-style (array di char terminate da '\0') e std::string (la classe moderna e consigliata).
8.1 Stringhe C-style
Un array di char termina con il carattere '\0' (null terminator). Sono scomode e pericolose, ma è importante conoscerle.
#include <cstring> char nome[20] = "Marco"; // 5 char + '\0' char saluto[] = "Ciao"; // dimensione dedotta // Funzioni della libreria <cstring> std::strlen(nome); // lunghezza: 5 std::strcpy(dest, src); // copia std::strcat(dest, src); // concatena std::strcmp(a, b); // confronta: 0 se uguali
Le stringhe C-style non controllano i limiti: strcpy su un buffer troppo piccolo causa overflow e crash. Usa sempre std::string nel C++ moderno.
8.2 std::string
La classe std::string è molto più comoda: gestisce la memoria automaticamente, si concatena con +, si confronta con ==.
#include <string> #include <iostream> std::string nome = "Marco"; std::string cognome = "Rossi"; // Concatenazione std::string completo = nome + " " + cognome; // "Marco Rossi" // Lunghezza std::cout << completo.length() << std::endl; // 11 std::cout << completo.size() << std::endl; // 11 (alias) // Accesso ai caratteri std::cout << nome[0] << std::endl; // 'M' std::cout << nome.at(1) << std::endl; // 'a' (con controllo limiti) // Confronto if (nome == "Marco") std::cout << "Uguale"; // Sottostringa std::string sub = completo.substr(0, 5); // "Marco" // Ricerca size_t pos = completo.find("Rossi"); // 6 // Inserimento e cancellazione completo.insert(0, "Sig. "); // "Sig. Marco Rossi" completo.erase(0, 5); // "Marco Rossi" completo.replace(0, 5, "Luca"); // "Luca Rossi"
8.3 Conversione numero ↔ stringa
// Da numero a stringa std::string s = std::to_string(42); // "42" std::string pi = std::to_string(3.14); // "3.140000" // Da stringa a numero (C++11) int n = std::stoi("42"); // 42 double d = std::stod("3.14"); // 3.14 long long ll = std::stoll("9999999999");
std::stoi lancia un'eccezione std::invalid_argument se la stringa non è un numero, e std::out_of_range se il numero è troppo grande. Vedi il capitolo Eccezioni.
9Funzioni Intermedio
Una funzione è un blocco di codice riutilizzabile che esegue un compito specifico. Permette di organizzare il programma, evitare duplicazioni e testare facilmente.
9.1 Dichiarazione, definizione e chiamata
// Dichiarazione (prototipo) — dice al compilatore che esiste int somma(int a, int b); // Definizione — il corpo della funzione int somma(int a, int b) { return a + b; } int main() { int r = somma(3, 5); // chiamata std::cout << r << std::endl; // 8 return 0; }
9.2 Parametri per valore, per riferimento, per puntatore
Questo è uno dei concetti più importanti in C++.
Per valore
void f(int x)
La funzione riceve una copia. Modifiche non influenzano l'originale. Costoso per oggetti grandi.
Per riferimento
void f(int& x)
La funzione lavora sull'originale. Modifiche visibili. Usa const int& per non modificare.
void perValore(int x) { x = 100; } // copia, non cambia fuori void perRiferimento(int& x) { x = 100; } // modifica l'originale void perPuntatore(int* p) { *p = 100; } // modifica tramite puntatore int main() { int a = 1; perValore(a); // a resta 1 perRiferimento(a); // a diventa 100 perPuntatore(&a); // a resta 100 return 0; }
Per tipi base (int, double, char) usa il passaggio per valore (copia poco costosa). Per oggetti grandi (std::string, std::vector, struct) usa const reference: const std::string&.
9.3 Valori di default
void saluta(std::string nome, std::string saluto = "Ciao") { std::cout << saluto << ", " << nome << std::endl; } saluta("Marco"); // Ciao, Marco saluta("Marco", "Buongiorno"); // Buongiorno, Marco
9.4 Overloading (funzioni con lo stesso nome)
Puoi definire più funzioni con lo stesso nome ma parametri diversi. Il compilatore sceglie quella giusta in base agli argomenti.
int somma(int a, int b) { return a + b; } double somma(double a, double b) { return a + b; } int somma(int a, int b, int c) { return a + b + c; } std::cout << somma(2, 3); // 5 (int) std::cout << somma(2.5, 3.1); // 5.6 (double) std::cout << somma(1, 2, 3); // 6
9.5 Ricorsione
Una funzione che chiama se stessa. Utile per problemi "divisibili" (es. fattoriale, Fibonacci, torre di Hanoi).
// Fattoriale ricorsivo long long fattoriale(int n) { if (n <= 1) return 1; // caso base return n * fattoriale(n - 1); // passo ricorsivo } // Fibonacci ricorsivo int fib(int n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); }
La ricorsione consuma memoria (lo stack). Se non c'è un caso base, o se la ricorsione è troppo profonda, il programma va in stack overflow. Per Fibonacci meglio la versione iterativa (molto più veloce).
10Puntatori Intermedio
Un puntatore è una variabile che contiene un indirizzo di memoria. È una delle feature più potenti (e pericolose) di C++.
10.1 Dichiarazione e operatori
&(indirizzo di): restituisce l'indirizzo di una variabile*(dereferenziazione): accede al valore puntato
int a = 42; int* p = &a; // p contiene l'indirizzo di a std::cout << a << std::endl; // 42 std::cout << p << std::endl; // 0x7ffee... (indirizzo) std::cout << *p << std::endl; // 42 (valore puntato) *p = 100; // modifica a tramite p std::cout << a << std::endl; // 100
10.2 Puntatore nullo
int* p = nullptr; // C++11, preferito if (p != nullptr) { std::cout << *p; // solo se non è nullo } // Mai dereferenziare un puntatore nullo! Undefined behavior.
10.3 Aritmetica dei puntatori
Puoi spostarti nella memoria. p + 1 avanza di sizeof(*p) byte.
int arr[5] = {10, 20, 30, 40, 50}; int* p = arr; // punta al primo elemento std::cout << *p << std::endl; // 10 std::cout << *(p + 1) << std::endl; // 20 std::cout << p[2] << std::endl; // 30 (equivalente a *(p+2)) // Scorrimento con puntatore for (int* q = arr; q != arr + 5; q++) { std::cout << *q << " "; }
10.4 Puntatori e array
In C++ il nome di un array decade in un puntatore al primo elemento. arr è equivalente a &arr[0].
I puntatori servono per: (1) passare grandi strutture senza copiarle, (2) allocare memoria dinamica con new, (3) implementare strutture dati come liste e alberi, (4) interfacciarsi con C e l'hardware.
11Reference Intermedio
Una reference è un alias di una variabile: un altro nome per la stessa locazione di memoria. Si dichiara con &.
int a = 10; int& r = a; // r è un alias di a r = 20; // modifica a std::cout << a << std::endl; // 20 // Una reference DEVE essere inizializzata // int& r; // ERRORE // Una reference NON può essere riassegnata int b = 30; r = b; // copia il valore di b in a, non cambia il riferimento! std::cout << a << std::endl; // 30
11.1 Reference vs puntatori
Puntatori
Possono essere nulli, riassegnati, hanno aritmetica. Sintassi più scomoda (*, ->).
Reference
Sempre validi, non riassegnabili, sintassi come variabile normale. Più sicuri.
11.2 Reference const
Una const T& è il modo standard per passare un oggetto grande senza copiarlo e senza permettere modifiche.
// Efficiente: non copia la stringa, non può modificarla void stampa(const std::string& s) { std::cout << s << std::endl; // s = "ciao"; // ERRORE: const } // Accetta sia lvalue che rvalue std::string nome = "Marco"; stampa(nome); // OK stampa("Ciao mondo"); // OK (temporaneo)
12Struct Intermedio
Una struct raggruppa più variabili di tipo diverso sotto un unico nome. È il mattone base della programmazione orientata agli oggetti.
struct Punto { double x; double y; }; int main() { Punto p1; // dichiarazione p1.x = 3.0; p1.y = 4.0; Punto p2 = {1.0, 2.0}; // inizializzazione Punto p3{5.0, 6.0}; // C++11 std::cout << p1.x << "," << p1.y << std::endl; // 3,4 return 0; }
12.1 Struct annidate e array di struct
struct Data { int giorno, mese, anno; }; struct Studente { std::string nome; int matricola; Data nascita; double voti[5]; }; Studente s; s.nome = "Marco"; s.matricola = 12345; s.nascita.giorno = 15; s.nascita.mese = 3; s.nascita.anno = 2008; s.voti[0] = 8.5; // Array di struct Studente classe[25];
12.2 Funzioni e struct
struct Punto { double x, y; }; // Passaggio per riferimento per modificare void sposta(Punto& p, double dx, double dy) { p.x += dx; p.y += dy; } // Passaggio per const reference per leggere (efficiente) double distanza(const Punto& a, const Punto& b) { double dx = a.x - b.x; double dy = a.y - b.y; return std::sqrt(dx*dx + dy*dy); }
In C++ struct e class sono quasi identiche. L'unica differenza: i membri di struct sono pubblici di default, quelli di class sono privati. Per convenzione, si usa struct per aggregati di dati semplici, class per oggetti con comportamento.
13Programmazione a oggetti Intermedio
La Programmazione Orientata agli Oggetti (OOP) organizza il codice in classi che incapsulano dati e comportamenti.
13.1 Classi e oggetti
class ContoBancario { private: std::string intestatario; double saldo; public: // Costruttore ContoBancario(std::string nome, double iniziale) { intestatario = nome; saldo = iniziale; } // Metodi void deposita(double importo) { if (importo > 0) saldo += importo; } bool preleva(double importo) { if (importo > saldo) return false; saldo -= importo; return true; } double getSaldo() const { return saldo; } std::string getIntestatario() const { return intestatario; } }; int main() { ContoBancario c("Marco", 100.0); c.deposita(50); c.preleva(30); std::cout << c.getSaldo() << std::endl; // 120 // c.saldo = 0; // ERRORE: privato return 0; }
13.2 Incapsulamento: public, private, protected
| Specificatore | Accessibile da... |
|---|---|
public | Tutti |
private | Solo la classe stessa |
protected | La classe e le sue sottoclassi |
13.3 Costruttori e distruttori
class Rettangolo { private: double base, altezza; public: // Costruttore di default Rettangolo() : base(1), altezza(1) {} // Costruttore con parametri (lista di inizializzazione) Rettangolo(double b, double h) : base(b), altezza(h) {} // Distruttore (chiamato automaticamente alla fine) ~Rettangolo() { std::cout << "Distrutto" << std::endl; } double area() const { return base * altezza; } }; Rettangolo r1; // 1x1 Rettangolo r2(5, 3); // 5x3 Rettangolo r3{4, 2}; // C++11
13.4 Ereditarietà
Una classe figlia eredita attributi e metodi della classe madre, e può aggiungerne o sovrascriverli.
class Animale { protected: std::string nome; public: Animale(std::string n) : nome(n) {} virtual void verso() const { std::cout << "..." << std::endl; } virtual ~Animale() {} // distruttore virtuale (importante!) }; class Cane : public Animale { public: Cane(std::string n) : Animale(n) {} void verso() const override { std::cout << "Bau!" << std::endl; } }; class Gatto : public Animale { public: Gatto(std::string n) : Animale(n) {} void verso() const override { std::cout << "Miao" << std::endl; } }; int main() { Cane c("Rex"); Gatto g("Whiskers"); c.verso(); // Bau! g.verso(); // Miao // Polimorfismo Animale* a = &c; a->verso(); // Bau! (grazie a virtual) }
Con virtual, la funzione chiamata dipende dal tipo reale dell'oggetto, non dal tipo del puntatore. È il cuore dell'OOP: puoi trattare oggetti diversi allo stesso modo.
14std::vector Intermedio
std::vector è l'array dinamico della libreria standard: cresce e si riduce automaticamente. È la struttura dati più usata in C++.
#include <vector> // Dichiarazione e inizializzazione std::vector<int> v; // vuoto std::vector<int> v2 = {1, 2, 3}; std::vector<int> v3(5, 10); // 5 elementi tutti = 10 // Aggiungere elementi v.push_back(42); // aggiunge in coda v.push_back(43); // Rimuovere l'ultimo v.pop_back(); // Accesso v[0] = 100; // no controllo limiti v.at(0) = 200; // con controllo, lancia eccezione // Dimensione std::cout << v.size() << std::endl; std::cout << v.empty() << std::endl; // true se vuoto // Iterazione for (int x : v) std::cout << x << " "; // Con indice for (size_t i = 0; i < v.size(); i++) std::cout << v[i] << " "; // Svuotare v.clear();
14.1 Funzioni comuni
| Metodo | Cosa fa |
|---|---|
push_back(x) | Aggiunge x in coda |
pop_back() | Rimuove l'ultimo elemento |
size() | Numero di elementi |
empty() | true se vuoto |
clear() | Svuota il vector |
at(i) | Accede con controllo limiti |
front() / back() | Primo / ultimo elemento |
insert(it, x) | Inserisce in posizione |
erase(it) | Rimuove in posizione |
resize(n) | Ridimensiona |
14.2 Esempio: media e voto massimo
#include <vector> #include <iostream> #include <algorithm> std::vector<int> voti = {6, 7, 9, 5, 8}; // Somma e media int somma = 0; for (int v : voti) somma += v; double media = static_cast<double>(somma) / voti.size(); // Massimo (con algorithm) int massimo = *std::max_element(voti.begin(), voti.end()); // Ordinamento (con algorithm) std::sort(voti.begin(), voti.end());
★20 esercizi · Livello Intermedio
Conta parole
Data una frase, conta quante parole contiene (separate da spazi).
Soluzione
#include <sstream> std::string frase; std::getline(std::cin, frase); std::istringstream iss(frase); std::string parola; int cont = 0; while (iss >> parola) cont++; std::cout << cont;
Inverti stringa
Leggi una stringa e stampala al contrario.
Soluzione
#include <algorithm> std::string s; std::cin >> s; std::reverse(s.begin(), s.end()); std::cout << s;
Conta vocali (funzione)
Scrivi una funzione che conta le vocali in una stringa.
Soluzione
int contaVocali(const std::string& s) { int c = 0; for (char ch : s) { ch = std::tolower(ch); if (ch=='a'||ch=='e'||ch=='i'||ch=='o'||ch=='u') c++; } return c; }
Swap con puntatori
Scambia due valori usando puntatori.
Soluzione
void swapPtr(int* a, int* b) { int t = *a; *a = *b; *b = t; } // Oppure con reference void swapRef(int& a, int& b) { int t = a; a = b; b = t; }
Distanza tra punti (struct)
Definisci una struct Punto e calcola la distanza tra due punti.
Soluzione
#include <cmath> struct Punto { double x, y; }; double dist(const Punto& a, const Punto& b) { return std::sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)); }
Classe Cerchio
Crea una classe Cerchio con raggio e metodi per area e circonferenza.
Soluzione
class Cerchio { private: double raggio; public: Cerchio(double r) : raggio(r) {} double area() const { return 3.14159 * raggio * raggio; } double circonferenza() const { return 2 * 3.14159 * raggio; } };
Min e max con reference
Scrivi una funzione che riceve un vector e restituisce min e max tramite reference.
Soluzione
void minMax(const std::vector<int>& v, int& min, int& max) { min = max = v[0]; for (int x : v) { if (x < min) min = x; if (x > max) max = x; } }
Numero primo
Scrivi una funzione che dice se un numero è primo.
Soluzione
bool primo(int n) { if (n < 2) return false; for (int i = 2; i*i <= n; i++) if (n % i == 0) return false; return true; }
Fattoriale ricorsivo
Calcola N! ricorsivamente.
Soluzione
long long fat(int n) { if (n <= 1) return 1; return n * fat(n-1); }
Media con vector
Calcola la media di un vector di double.
Soluzione
double media(const std::vector<double>& v) { if (v.empty()) return 0; double s = 0; for (double x : v) s += x; return s / v.size(); }
Fibonacci iterativo
Stampa i primi N numeri di Fibonacci.
Soluzione
long long a = 0, b = 1; for (int i = 0; i < n; i++) { std::cout << a << " "; long long t = a + b; a = b; b = t; }
Palindromo (funzione)
Verifica se una stringa è palindroma.
Soluzione
bool palindromo(const std::string& s) { int n = s.size(); for (int i = 0; i < n/2; i++) if (s[i] != s[n-1-i]) return false; return true; }
Conta occorrenze
Conta quante volte un carattere appare in una stringa.
Soluzione
int conta(const std::string& s, char c) { int n = 0; for (char x : s) if (x == c) n++; return n; }
Rimuovi duplicati
Da un vector, rimuovi i duplicati (mantenendo l'ordine).
Soluzione
#include <set> std::vector<int> risultato; std::set<int> visti; for (int x : v) { if (visti.insert(x).second) risultato.push_back(x); }
Somma cifre
Calcola la somma delle cifre di un numero.
Soluzione
int sommaCifre(int n) { int s = 0; while (n > 0) { s += n % 10; n /= 10; } return s; }
Classe Studente
Crea una classe Studente con nome, media e metodo per la promozione.
Soluzione
class Studente { private: std::string nome; double media; public: Studente(std::string n, double m) : nome(n), media(m) {} bool promosso() const { return media >= 6.0; } std::string getNome() const { return nome; } };
Array dinamico con vector
Leggi N numeri in un vector e stampali al contrario.
Soluzione
int n; std::cin >> n; std::vector<int> v(n); for (int& x : v) std::cin >> x; for (auto it = v.rbegin(); it != v.rend(); ++it) std::cout << *it << " ";
GCD (MCD)
Calcola il massimo comune divisore con l'algoritmo di Euclide.
Soluzione
int gcd(int a, int b) { while (b != 0) { int t = b; b = a % b; a = t; } return a; }
Torre di Hanoi
Risolvi la torre di Hanoi in modo ricorsivo.
Soluzione
void hanoi(int n, char da, char a, char via) { if (n == 1) { std::cout << da << "->" << a << std::endl; return; } hanoi(n-1, da, via, a); std::cout << da << "->" << a << std::endl; hanoi(n-1, via, a, da); }
Rubrica con vector di struct
Crea una rubrica con nome e telefono, e cerca per nome.
Soluzione
struct Contatto { std::string nome; std::string tel; }; std::vector<Contatto> rubrica = { {"Marco", "333-1234567"}, {"Sofia", "347-7654321"} }; void cerca(const std::string& nome) { for (const auto& c : rubrica) if (c.nome == nome) std::cout << c.tel << std::endl; }
15STL Avanzato
La Standard Template Library (STL) è una collezione di strutture dati e algoritmi pronti all'uso. È uno dei punti di forza del C++ moderno: non devi reinventare la ruota.
15.1 Panoramica dei container
| Container | Descrizione | Uso tipico |
|---|---|---|
vector | Array dinamico | Sequenze, accesso per indice |
array | Array a dimensione fissa | Array piccoli e noti a compile-time |
list | Lista doppiamente collegata | Inserimenti/rimozioni frequenti in mezzo |
deque | Coda a doppia estremità | Inserimenti in testa e in coda |
set | Insieme ordinato di elementi unici | Rimuovere duplicati, ricerca veloce |
map | Dizionario chiave→valore ordinato | Rubrica, contatori, lookup |
unordered_set | Set senza ordine (hash table) | Ricerca ancora più veloce |
unordered_map | Map senza ordine (hash table) | Dizionari dove l'ordine non conta |
stack | Pila LIFO | Undo, parsing |
queue | Coda FIFO | Scheduler, BFS |
15.2 std::map — dizionario
Una map associa una chiave a un valore. Le chiavi sono uniche e ordinate.
#include <map> std::map<std::string, int> eta; // Inserimento eta["Marco"] = 17; eta["Sofia"] = 16; eta["Luca"] = 18; // Accesso std::cout << eta["Marco"] << std::endl; // 17 // Ricerca sicura auto it = eta.find("Anna"); if (it != eta.end()) { std::cout << it->second << std::endl; } else { std::cout << "Non trovato" << std::endl; } // Conteggio occorrenze (molto comune) std::map<std::string, int> conteggio; std::string parola; while (std::cin >> parola) { conteggio[parola]++; // incrementa (crea a 0 se assente) } // Iterazione (le chiavi sono ordinate!) for (const auto& [chiave, valore] : conteggio) { std::cout << chiave << ": " << valore << std::endl; }
15.3 std::set — insieme ordinato
Un set contiene elementi unici, ordinati. Ottimo per rimuovere duplicati o testare appartenenza.
#include <set> std::set<int> s; s.insert(3); s.insert(1); s.insert(2); s.insert(1); // duplicato, ignorato // Ricerca if (s.count(2)) std::cout << "Contiene 2"; // Iterazione (ordine crescente) for (int x : s) std::cout << x << " "; // 1 2 3
15.4 Algoritmi STL
La libreria <algorithm> fornisce decine di algoritmi pronti all'uso.
#include <algorithm> #include <vector> std::vector<int> v = {5, 2, 8, 1, 9, 3}; // Ordinamento (O(n log n)) std::sort(v.begin(), v.end()); // Ordine decrescente std::sort(v.begin(), v.end(), std::greater<int>()); // Minimo e massimo auto min = std::min_element(v.begin(), v.end()); auto max = std::max_element(v.begin(), v.end()); // Ricerca auto it = std::find(v.begin(), v.end(), 8); // Conteggio int n = std::count(v.begin(), v.end(), 3); // Somma (in <numeric>) #include <numeric> int somma = std::accumulate(v.begin(), v.end(), 0); // Inversione std::reverse(v.begin(), v.end()); // Rimuovere duplicati (dopo sort) v.erase(std::unique(v.begin(), v.end()), v.end()); // Trasformazione (map) std::vector<int> doppi(v.size()); std::transform(v.begin(), v.end(), doppi.begin(), [](int x) { return x * 2; });
16Template Avanzato
I template permettono di scrivere codice generico: funzioni e classi che funzionano con qualsiasi tipo. È il meccanismo alla base della STL.
16.1 Funzioni template
// Funzione generica per il massimo template<typename T> T massimo(T a, T b) { return (a > b) ? a : b; } std::cout << massimo(3, 7) << std::endl; // 7 (int) std::cout << massimo(3.14, 2.71) << std::endl; // 3.14 (double) std::cout << massimo(std::string("a"), std::string("b"));
16.2 Classi template
// Una coppia generica template<typename K, typename V> class Coppia { private: K chiave; V valore; public: Coppia(K k, V v) : chiave(k), valore(v) {} K getChiave() const { return chiave; } V getValore() const { return valore; } }; Coppia<std::string, int> p("Marco", 17); std::cout << p.getChiave() << " ha " << p.getValore() << " anni";
Prima dei template si usavano le macro (#define MAX(a,b) ...), ma sono pericolose (nessun controllo di tipo, problemi di parentesi). I template sono la soluzione moderna.
17Eccezioni Avanzato
Le eccezioni gestiscono gli errori in modo pulito: separano il codice "normale" dalla gestione degli errori.
17.1 try / catch / throw
#include <stdexcept> double dividi(double a, double b) { if (b == 0) { throw std::invalid_argument("Divisione per zero"); } return a / b; } int main() { try { double r = dividi(10, 0); std::cout << r << std::endl; } catch (const std::exception& e) { std::cout << "Errore: " << e.what() << std::endl; } return 0; }
17.2 Eccezioni standard
| Eccezione | Quando viene lanciata |
|---|---|
std::invalid_argument | Argomento non valido (es. stoi("abc")) |
std::out_of_range | Accesso fuori range (es. vector::at()) |
std::runtime_error | Errore generico a runtime |
std::bad_alloc | Allocazione di memoria fallita |
std::logic_error | Errore logico del programmatore |
17.3 Catturare più tipi di eccezione
try { int n = std::stoi("abc"); // lancia invalid_argument } catch (const std::invalid_argument& e) { std::cout << "Formato non valido: " << e.what() << std::endl; } catch (const std::out_of_range& e) { std::cout << "Numero troppo grande: " << e.what() << std::endl; } catch (...) { std::cout << "Errore sconosciuto" << std::endl; }
Puoi creare eccezioni personalizzate ereditando da std::runtime_error o std::exception. Utile per gestire errori specifici della tua applicazione.
18Input/Output su file Avanzato
Per leggere e scrivere file si usano ifstream (input) e ofstream (output), definite in <fstream>.
18.1 Scrivere su file
#include <fstream> #include <iostream> std::ofstream file("output.txt"); if (!file.is_open()) { std::cerr << "Impossibile aprire il file" << std::endl; return 1; } file << "Ciao, file!" << std::endl; file << 42 << " " << 3.14 << std::endl; file.close(); // chiusura esplicita (o automatica alla distruzione)
18.2 Leggere da file
#include <fstream> #include <iostream> #include <string> std::ifstream file("input.txt"); if (!file.is_open()) { std::cerr << "File non trovato" << std::endl; return 1; } // Leggere parola per parola std::string parola; while (file >> parola) { std::cout << parola << std::endl; } // Leggere riga per riga std::string riga; while (std::getline(file, riga)) { std::cout << riga << std::endl; } file.close();
18.3 Modalità di apertura
| Modalità | Effetto |
|---|---|
std::ios::in | Apri in lettura |
std::ios::out | Apri in scrittura (tronca il file) |
std::ios::app | Aggiungi in coda (senza cancellare) |
std::ios::trunc | Tronca il file se esiste |
std::ios::binary | Modalità binaria |
// Aggiungi a un file esistente std::ofstream log("log.txt", std::ios::app); log << "Nuova riga di log" << std::endl; // File binario std::ofstream bin("data.bin", std::ios::binary);
Un file potrebbe non esistere, non avere i permessi, essere bloccato. Controlla sempre is_open() prima di leggere o scrivere.
19Memoria dinamica Avanzato
La memoria dinamica si alloca a runtime con new e si libera con delete. È la base per strutture dati a dimensione variabile.
19.1 new e delete
// Singolo oggetto int* p = new int(42); std::cout << *p << std::endl; delete p; // libera la memoria // Array dinamico int n = 10; int* arr = new int[n]; for (int i = 0; i < n; i++) arr[i] = i * 2; delete[] arr; // attenzione alle parentesi quadre! // Oggetto di classe auto* p2 = new std::string("Ciao"); delete p2;
Se dimentichi delete, la memoria resta occupata fino alla chiusura del programma. In un ciclo, questo esaurisce la RAM. Regola: ogni new deve avere un delete.
19.2 Errori comuni
// Memory leak int* p1 = new int(10); // ... dimentico delete // Double free: libero due volte int* p2 = new int(20); delete p2; // delete p2; // ERRORE: double free // Dangling pointer: uso dopo delete int* p3 = new int(30); delete p3; // std::cout << *p3; // ERRORE: memoria non più valida
Il prossimo capitolo spiega come unique_ptr e shared_ptr eliminino completamente gli errori di memoria automatica. Sono la soluzione preferita nel C++ moderno.
20Smart pointer Avanzato
Gli smart pointer (C++11) gestiscono la memoria automaticamente: quando l'oggetto non è più usato, la memoria viene liberata da sola. Eliminano memory leak e dangling pointer.
20.1 unique_ptr
Un unique_ptr possiede in modo esclusivo un oggetto: non può essere copiato, solo spostato.
#include <memory> // Creazione std::unique_ptr<int> p = std::make_unique<int>(42); std::cout << *p << std::endl; // Array auto arr = std::make_unique<int[]>(5); arr[0] = 10; // Non serve delete! // Quando p esce dallo scope, libera la memoria automaticamente // Trasferimento di proprietà (move) std::unique_ptr<int> q = std::move(p); // ora q possiede l'oggetto, p è vuoto
20.2 shared_ptr
Un shared_ptr permette a più puntatori di condividere lo stesso oggetto. La memoria è liberata quando l'ultimo riferimento sparisce.
auto p1 = std::make_shared<int>(100); std::shared_ptr<int> p2 = p1; // condivide std::cout << p1.use_count() << std::endl; // 2 *p2 = 200; // modifica per tutti p1.reset(); std::cout << p2.use_count() << std::endl; // 1
20.3 weak_ptr
Un weak_ptr è un riferimento non-possedente a un oggetto gestito da shared_ptr. Serve per evitare cicli di riferimenti (che causano memory leak).
Mai usare new e delete direttamente nel C++ moderno. Usa sempre make_unique o make_shared. Gli smart pointer gestiscono la memoria per te.
21C++ moderno Avanzato
Dal C++11 in poi, il linguaggio ha ricevuto tantissime feature che rendono il codice più sicuro e leggibile.
21.1 auto e range-for
// auto deduce il tipo auto n = 42; // int auto pi = 3.14; // double auto nome = std::string("Marco"); // Range-for std::vector<int> v = {1, 2, 3}; for (auto x : v) std::cout << x << " "; // Modifica con auto& for (auto& x : v) x *= 2;
21.2 Lambda
Le lambda sono funzioni anonime definite al volo. Utilissime per callback e algoritmi.
// Lambda base auto somma = [](int a, int b) { return a + b; }; std::cout << somma(3, 4) << std::endl; // 7 // Lambda con cattura per riferimento int contatore = 0; auto incrementa = [&contatore]() { contatore++; }; incrementa(); incrementa(); std::cout << contatore << std::endl; // 2 // Uso con sort per ordinamento custom std::vector<std::string> nomi = {"Marco", "Anna", "Luca"}; std::sort(nomi.begin(), nomi.end(), [](const std::string& a, const std::string& b) { return a.size() < b.size(); // ordina per lunghezza });
21.3 Structured bindings (C++17)
// Iterare una map in modo pulito std::map<std::string, int> eta = {{"Marco", 17}, {"Sofia", 16}}; for (const auto& [nome, anni] : eta) { std::cout << nome << ": " << anni << std::endl; } // Con pair auto [min, max] = std::minmax(5, 3);
21.4 constexpr
constexpr indica che un valore o una funzione sono calcolati a compile-time, non a runtime. Più veloce.
constexpr int quadrato(int x) { return x * x; } constexpr int risultato = quadrato(5); // calcolato a compile-time
21.5 Riepilogo feature moderne
| Feature | Standard | Utilità |
|---|---|---|
auto | C++11 | Deduzione del tipo |
nullptr | C++11 | Puntatore nullo sicuro |
| Range-for | C++11 | Iterazione più semplice |
| Lambda | C++11 | Funzioni anonime |
unique_ptr, shared_ptr | C++11 | Gestione memoria automatica |
constexpr | C++11 | Calcoli a compile-time |
make_unique | C++14 | Creazione smart pointer |
| Structured bindings | C++17 | Decomposizione in variabili |
std::optional | C++17 | Valore opzionale |
std::string_view | C++17 | Stringhe senza copia |
| Concepts | C++20 | Vincoli sui template |
| Ranges | C++20 | Pipeline di algoritmi |
| Modules | C++20 | Alternativa a #include |
★20 esercizi · Livello Avanzato
Conta parole con map
Leggi una frase e conta le occorrenze di ogni parola.
Soluzione
#include <map> #include <sstream> std::map<std::string, int> conteggio; std::string frase, parola; std::getline(std::cin, frase); std::istringstream iss(frase); while (iss >> parola) conteggio[parola]++; for (const auto& [p, n] : conteggio) std::cout << p << ": " << n << std::endl;
Funzione template max
Scrivi una funzione template che restituisce il massimo di tre valori.
Soluzione
template<typename T> T max3(T a, T b, T c) { return std::max(a, std::max(b, c)); }
Gestione eccezioni
Leggi un numero da input e gestisci eventuali errori.
Soluzione
std::string s; std::cin >> s; try { int n = std::stoi(s); std::cout << "Numero: " << n << std::endl; } catch (const std::invalid_argument&) { std::cout << "Non è un numero!" << std::endl; } catch (const std::out_of_range&) { std::cout << "Numero troppo grande" << std::endl; }
Scrivi su file
Chiedi N numeri all'utente e salvali in un file.
Soluzione
#include <fstream> std::ofstream f("numeri.txt"); int n; std::cin >> n; for (int i = 0; i < n; i++) { int x; std::cin >> x; f << x << std::endl; } f.close();
Leggi file
Leggi un file riga per riga e conta quante righe contiene.
Soluzione
#include <fstream> #include <string> std::ifstream f("file.txt"); std::string riga; int cont = 0; while (std::getline(f, riga)) cont++; std::cout << cont << std::endl;
unique_ptr
Crea un array dinamico di N interi con unique_ptr.
Soluzione
#include <memory> int n; std::cin >> n; auto arr = std::make_unique<int[]>(n); for (int i = 0; i < n; i++) arr[i] = i; // Nessun delete necessario
Lambda per ordinamento
Ordina un vector di struct per un campo specifico usando una lambda.
Soluzione
struct Studente { std::string nome; double media; }; std::vector<Studente> v = {"Marco", 7.5}, {"Sofia", 9.0}, ...}; std::sort(v.begin(), v.end(), [](const Studente& a, const Studente& b) { return a.media > b.media; // decrescente });
Set di parole uniche
Leggi parole finché non digiti "stop" e stampa quelle uniche.
Soluzione
std::set<std::string> uniche; std::string p; while (std::cin >> p && p != "stop") uniche.insert(p); for (const auto& w : uniche) std::cout << w << std::endl;
Matrice con vector di vector
Crea una matrice 3x3 usando vector di vector e stampane la diagonale.
Soluzione
std::vector<std::vector<int>> m = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; for (int i = 0; i < 3; i++) std::cout << m[i][i] << " "; // 1 5 9
Classe template Stack
Implementa una pila generica con template.
Soluzione
template<typename T> class Stack { private: std::vector<T> dati; public: void push(const T& x) { dati.push_back(x); } void pop() { if (!dati.empty()) dati.pop_back(); } T& top() { return dati.back(); } bool empty() const { return dati.empty(); } };
Conta vocali con lambda
Conta le vocali in un vector di stringhe con una lambda.
Soluzione
auto contaVocali = [](const std::string& s) { int c = 0; for (char ch : s) { ch = std::tolower(ch); if (ch=='a'||ch=='e'||ch=='i'||ch=='o'||ch=='u') c++; } return c; }; std::cout << contaVocali("Ciao mondo"); // 4
Ordinamento personalizzato
Ordina una map per valore invece che per chiave.
Soluzione
std::map<std::string, int> m = {{"a", 3}, {"b", 1}, {"c", 2}}; std::vector<std::pair<std::string, int>> v(m.begin(), m.end()); std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.second > b.second; });
Registra errori su file
Ogni volta che viene lanciata un'eccezione, scrivi il messaggio su un file di log.
Soluzione
try { // codice rischioso } catch (const std::exception& e) { std::ofstream log("errori.log", std::ios::app); log << "[ERRORE] " << e.what() << std::endl; }
shared_ptr condiviso
Crea un oggetto condiviso tra due funzioni con shared_ptr.
Soluzione
auto contatore = std::make_shared<int>(0); auto incrementa = [contatore]() { (*contatore)++; }; incrementa(); incrementa(); std::cout << *contatore; // 2
Converti CSV in tabella
Leggi un file CSV e stampa i dati in tabella formattata.
Soluzione
#include <fstream> #include <sstream> #include <iomanip> std::ifstream f("dati.csv"); std::string riga; while (std::getline(f, riga)) { std::istringstream iss(riga); std::string campo; while (std::getline(iss, campo, ',')) std::cout << std::setw(15) << campo; std::cout << std::endl; }
Password con hash
Chiedi una password e verifica che rispetti i requisiti (maiuscole, numeri, lunghezza).
Soluzione
bool valida(const std::string& pwd) { if (pwd.size() < 8) return false; bool maiusc = false, num = false; for (char c : pwd) { if (std::isupper(c)) maiusc = true; if (std::isdigit(c)) num = true; } return maiusc && num; }
Statistiche di testo
Leggi un file e conta: righe, parole, caratteri.
Soluzione
std::ifstream f("testo.txt"); std::string riga; int righe = 0, parole = 0, car = 0; while (std::getline(f, riga)) { righe++; car += riga.size() + 1; std::istringstream iss(riga); std::string w; while (iss >> w) parole++; }
Stack machine
Implementa una piccola calcolatrice a stack (RPN: Reverse Polish Notation).
Soluzione
#include <stack> std::stack<double> s; std::string token; while (std::cin >> token) { if (token == "+") { double b=s.top();s.pop(); double a=s.top();s.pop(); s.push(a+b); } else if (token == "-") { /* ... */ } else s.push(std::stod(token)); } std::cout << s.top();
JSON-like parser
Leggi una stringa nel formato chiave=valore,chiave=valore e riempi una map.
Soluzione
std::string input; std::getline(std::cin, input); std::map<std::string, std::string> m; std::istringstream iss(input); std::string coppia; while (std::getline(iss, coppia, ',')) { auto pos = coppia.find('='); if (pos != std::string::npos) m[coppia.substr(0, pos)] = coppia.substr(pos+1); }
Sistema bancario
Implementa una classe Banca che gestisce più conti con eccezioni.
Soluzione
class Banca { private: std::map<std::string, double> conti; public: void apriConto(const std::string& iban) { conti[iban] = 0; } void deposita(const std::string& iban, double x) { if (!conti.count(iban)) throw std::runtime_error("IBAN inesistente"); conti[iban] += x; } double saldo(const std::string& iban) const { auto it = conti.find(iban); if (it == conti.end()) throw std::runtime_error("IBAN inesistente"); return it->second; } };
22Progetto finale
Costruisci un gestionale di biblioteca: un programma completo che applica tutto ciò che hai imparato.
- Classi con incapsulamento (
Libro,Biblioteca) - Uso di
std::vectorestd::map - Gestione di file per salvare/caricare dati
- Eccezioni per la gestione degli errori
- Lambda e algoritmi STL
- Menu interattivo a console
Requisiti funzionali
- Aggiungi libro: titolo, autore, ISBN, disponibile
- Cerca libro: per titolo, autore o ISBN
- Prendi in prestito: marca come non disponibile
- Restituisci: marca come disponibile
- Elenco libri: mostra tutti con stato
- Salva/Carica: salva su file e ricarica all'avvio
- Gestione errori: eccezioni per ISBN duplicati, libri non trovati, ecc.
Struttura del progetto
biblioteca/ ├── main.cpp ├── libro.hpp / libro.cpp ├── biblioteca.hpp / biblioteca.cpp └── libri.txt (file dati)
libro.hpp — Header della classe Libro
#pragma once #include <string> class Libro { private: std::string titolo; std::string autore; std::string isbn; bool disponibile; public: Libro() : disponibile(true) {} Libro(const std::string& t, const std::string& a, const std::string& i) : titolo(t), autore(a), isbn(i), disponibile(true) {} std::string getTitolo() const { return titolo; } std::string getAutore() const { return autore; } std::string getISBN() const { return isbn; } bool isDisponibile() const { return disponibile; } void presta() { disponibile = false; } void restituisci() { disponibile = true; } std::string toString() const; };
libro.cpp — Implementazione
#include "libro.hpp" #include <sstream> std::string Libro::toString() const { std::ostringstream oss; oss << "[" << (disponibile ? "DISP" : "PREST") << "] " << titolo << " - " << autore << " (ISBN: " << isbn << ")"; return oss.str(); }
biblioteca.hpp — Header della classe Biblioteca
#pragma once #include "libro.hpp" #include <vector> #include <string> class Biblioteca { private: std::vector<Libro> libri; const std::string nomeFile = "libri.txt"; public: void aggiungi(const Libro& l); void presta(const std::string& isbn); void restituisci(const std::string& isbn); std::vector<Libro> cercaPerAutore(const std::string& autore) const; void stampaTutti() const; void salva() const; void carica(); };
biblioteca.cpp — Implementazione
#include "biblioteca.hpp" #include <iostream> #include <fstream> #include <sstream> #include <stdexcept> #include <algorithm> void Biblioteca::aggiungi(const Libro& l) { auto it = std::find_if(libri.begin(), libri.end(), [&l](const Libro& x) { return x.getISBN() == l.getISBN(); }); if (it != libri.end()) throw std::runtime_error("ISBN già esistente"); libri.push_back(l); } void Biblioteca::presta(const std::string& isbn) { for (auto& l : libri) { if (l.getISBN() == isbn) { if (!l.isDisponibile()) throw std::runtime_error("Libro già in prestito"); l.presta(); return; } } throw std::runtime_error("Libro non trovato"); } void Biblioteca::restituisci(const std::string& isbn) { for (auto& l : libri) { if (l.getISBN() == isbn) { l.restituisci(); return; } } throw std::runtime_error("Libro non trovato"); } std::vector<Libro> Biblioteca::cercaPerAutore(const std::string& autore) const { std::vector<Libro> risultato; std::copy_if(libri.begin(), libri.end(), std::back_inserter(risultato), [&autore](const Libro& l) { return l.getAutore() == autore; }); return risultato; } void Biblioteca::stampaTutti() const { if (libri.empty()) { std::cout << "Nessun libro in biblioteca.\n"; return; } for (const auto& l : libri) std::cout << l.toString() << std::endl; } void Biblioteca::salva() const { std::ofstream f(nomeFile); for (const auto& l : libri) { f << l.getTitolo() << "|" << l.getAutore() << "|" << l.getISBN() << "|" << l.isDisponibile() << std::endl; } } void Biblioteca::carica() { std::ifstream f(nomeFile); if (!f.is_open()) return; std::string riga; while (std::getline(f, riga)) { std::istringstream iss(riga); std::string titolo, autore, isbn, disp; std::getline(iss, titolo, '|'); std::getline(iss, autore, '|'); std::getline(iss, isbn, '|'); std::getline(iss, disp); Libro l(titolo, autore, isbn); if (disp == "0") l.presta(); libri.push_back(l); } }
main.cpp — Menu principale
#include "biblioteca.hpp" #include <iostream> #include <limits> void menu() { std::cout << "\n=== BIBLIOTECA ===\n" << "1) Aggiungi libro\n" << "2) Presta libro\n" << "3) Restituisci libro\n" << "4) Cerca per autore\n" << "5) Elenco libri\n" << "0) Esci\n" << "Scelta: "; } int main() { Biblioteca b; b.carica(); int scelta; while (true) { menu(); if (!(std::cin >> scelta)) break; std::cin.ignore(); try { if (scelta == 1) { std::string t, a, i; std::cout << "Titolo: "; std::getline(std::cin, t); std::cout << "Autore: "; std::getline(std::cin, a); std::cout << "ISBN: "; std::getline(std::cin, i); b.aggiungi(Libro(t, a, i)); std::cout << "Libro aggiunto.\n"; } else if (scelta == 2) { std::string i; std::cout << "ISBN: "; std::getline(std::cin, i); b.presta(i); std::cout << "Libro prestato.\n"; } else if (scelta == 3) { std::string i; std::cout << "ISBN: "; std::getline(std::cin, i); b.restituisci(i); std::cout << "Libro restituito.\n"; } else if (scelta == 4) { std::string autore; std::cout << "Autore: "; std::getline(std::cin, autore); for (const auto& l : b.cercaPerAutore(autore)) std::cout << l.toString() << std::endl; } else if (scelta == 5) { b.stampaTutti(); } else if (scelta == 0) { break; } } catch (const std::exception& e) { std::cout << "Errore: " << e.what() << std::endl; } } b.salva(); std::cout << "Dati salvati. Arrivederci!\n"; return 0; }
- Aggiungere la gestione degli utenti (studenti con prestiti)
- Data di scadenza e calcolo delle multe
- Ricerca per titolo parziale (con
find) - Ordinamento dei libri per titolo con
sort+ lambda - Interfaccia grafica con Qt o SFML
- Salvataggio in formato JSON