EuraStudy
Un linguaggio di programmazione è il sistema formale con cui traduciamo un algoritmo in un programma eseguibile dal calcolatore: ha una «sintassi» (le regole di forma per scrivere istruzioni corrette) e una «semantica» (il significato, cioè ciò che ogni istruzione fa eseguire alla macchina). In questo argomento si parte dal linguaggio macchina per arrivare ai linguaggi ad alto livello, distinguendo «compilazione» e «interpretazione», e si studiano gli elementi costitutivi del linguaggio — tipi di dato, variabili, operatori, strutture di controllo, sottoprogrammi — fino alla diagnosi e correzione degli errori. L'obiettivo è saper tradurre un algoritmo in un programma corretto, leggibile e modulare in un linguaggio ad alto livello (ad esempio Python), e saperlo verificare con prove ed esempi.
5 sezioni~27 min di lettura3 competenzeLivello Base 1 · Standard 3 · Approfondimento 1Verificato · 07/2026
livello base
È richiesto saper distinguere compilatore e interprete, riconoscere e usare tipi, variabili, operatori e le tre strutture di controllo, e scrivere semplici programmi corretti con funzioni in un linguaggio ad alto livello.
livello avanzato
L'indirizzo Scienze Applicate approfondisce la modularità (progettazione di sottoprogrammi con passaggio dei parametri ben scelto), la classificazione sistematica degli errori e una pratica consapevole di testing e debugging su problemi più articolati, anche in raccordo con Matematica.
Profondità di lettura: Approfondimento
Dimensione del testo: Standard
Dal sorgente all'esecuzione: compilazione e interpretazione
Un programma viene scritto in linguaggio ad alto livello. Descrivi che cosa accade, passo per passo, se viene compilato e che cosa accade se viene interpretato, evidenziando dove e quando emergono gli errori di sintassi.
In entrambi i casi si parte dal codice sorgente, cioè il testo scritto dal programmatore secondo le regole sintattiche del linguaggio.
Il compilatore analizza l'intero sorgente: se trova errori di sintassi li segnala tutti in questa fase e non produce alcun eseguibile. Se il sorgente è corretto, genera l'eseguibile in linguaggio macchina, che potrà essere lanciato anche molte volte senza ritradurre.
L'interprete legge ed esegue il sorgente un'istruzione alla volta: non crea un eseguibile separato e gli errori (anche di sintassi su una riga non ancora raggiunta) possono manifestarsi solo quando l'esecuzione arriva a quella riga.
La compilazione anticipa la diagnosi degli errori e premia la velocità di esecuzione; l'interpretazione premia la rapidità del ciclo di prova e la portabilità.
Risultato: Nella compilazione la traduzione è anticipata e completa (eseguibile + errori segnalati in fase di compilazione); nell'interpretazione la traduzione è contestuale all'esecuzione (nessun eseguibile, errori rilevati al momento dell'esecuzione della riga).
Errori frequenti
Approfondimento
La traduzione operata da un compilatore non è un unico passaggio, ma una catena di fasi ben distinte, oggetto di studio della teoria dei compilatori. L'analisi lessicale (scanner) spezza il testo in token elementari (parole chiave, identificatori, numeri, operatori); l'analisi sintattica (parser) verifica che i token rispettino la grammatica del linguaggio e ne costruisce l'albero sintattico; l'analisi semantica controlla la coerenza dei tipi e la dichiarazione delle variabili; seguono la generazione del codice intermedio, l'ottimizzazione e la generazione del codice macchina; infine il collegamento (linking) unisce il codice alle librerie usate. Il modello ibrido «bytecode + macchina virtuale» merita attenzione particolare: Java compila il sorgente in «bytecode», un linguaggio intermedio indipendente dalla piattaforma, che la JVM esegue interpretandolo o — per le porzioni più eseguite — compilandolo al volo in linguaggio macchina (compilazione JIT, just-in-time), conciliando così portabilità e prestazioni. Anche Python compila internamente in bytecode («.pyc») eseguito dalla propria macchina virtuale, il che mostra come «compilato» e «interpretato» non siano categorie nette ma estremi di un continuo. Sul piano concettuale i linguaggi si raggruppano poi in paradigmi — imperativo/procedurale, orientato agli oggetti, funzionale e logico — ciascuno con un diverso modo di descrivere il calcolo, dalla sequenza di comandi che modificano lo stato alla valutazione di funzioni senza effetti collaterali.
Ripasso attivo
Spiega in che cosa differiscono compilazione e interpretazione di un programma scritto in un linguaggio ad alto livello. Descrivi il percorso che porta dal codice sorgente all'esecuzione nei due casi, e indica almeno due vantaggi e due svantaggi di ciascuna strategia.
Richiamo attivo
Ricorda i punti chiave — poi rivela.
Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))
Tipi primitivi e operatori applicabili
Assegnazione
L'assegnazione non è un'uguaglianza: prende il valore attuale di x, vi somma 1 e rimette il risultato in x. La freccia rende esplicito il verso «da destra verso la variabile a sinistra».
Con a = 7, b = 2 e c = 3, calcola valore e tipo di: (1) a // b, (2) a % b, (3) a / b, (4) (a > b) and (b > c). Giustifica ogni passaggio.
7 diviso 2 dà quoziente intero 3; il tipo è int.
Il resto della divisione di 7 per 2 è 1; il tipo è int.
In Python l'operatore / dà sempre un float: 7 diviso 2 vale 3.5; il tipo è float.
Si valutano prima i confronti (a > b è 7 > 2 = True; b > c è 2 > 3 = False), poi l'and. True and False dà False; il tipo è bool.
Risultato: a // b = 3 (int); a % b = 1 (int); a / b = 3.5 (float); (a > b) and (b > c) = False (bool).
Errori frequenti
Approfondimento
La sintassi di un linguaggio si descrive rigorosamente con una grammatica formale, spesso scritta in notazione BNF (Backus-Naur Form): un insieme di regole di produzione che generano tutte e sole le frasi ben formate — la stessa nozione di grammatica che ritroveremo nella teoria della computazione e nei linguaggi di markup. Sul versante dei tipi, i linguaggi si distinguono lungo due assi indipendenti. La tipizzazione può essere statica (il tipo delle variabili è fissato e controllato alla compilazione, come in C e Java) oppure dinamica (il tipo è associato al valore e determinato a tempo di esecuzione, come in Python); ed è forte quando il linguaggio vieta conversioni implicite azzardate, debole quando le concede silenziosamente. Un concetto operativo importante è la valutazione in corto circuito (short-circuit) degli operatori logici: in «A and B», se A è falsa, B non viene nemmeno valutata perché il risultato è già deciso; ciò consente scritture idiomatiche e sicure come «k ≠ 0 and n/k > 1», dove il secondo test si esegue solo quando è lecito. Va infine ricordato che i tipi numerici hanno rappresentazione finita: gli interi a dimensione fissa possono andare in overflow oltre un certo intervallo, e i float approssimano i reali (0,1 non è rappresentabile esattamente in binario, così «0,1 + 0,2» non dà esattamente «0,3»), tema centrale ripreso nel capitolo di calcolo numerico. Distinguere il valore dal suo tipo e dalla sua rappresentazione in memoria è la chiave per prevedere il comportamento delle espressioni.
Ripasso attivo
Data la dichiarazione di tre variabili a = 7, b = 2 e c = 3, determina passo per passo il valore e il tipo del risultato delle espressioni a // b, a % b, a / b e (a > b) and (b > c), giustificando ogni passaggio con la precedenza degli operatori.
Richiamo attivo
Ricorda i punti chiave — poi rivela.
Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))
Le tre strutture di controllo come diagrammi di flusso
Scrivi un programma che legge un numero intero da tastiera e stampa «pari» se è divisibile per 2, altrimenti «dispari».
Si legge il dato e lo si converte in intero: n = int(input("Inserisci un intero: ")). La conversione è necessaria perché input() restituisce una stringa.
Un numero è pari se il resto della divisione per 2 è zero. La condizione corretta è n % 2 == 0.
Si usa if/else: if n % 2 == 0: print("pari") else: print("dispari"). I due rami sono mutuamente esclusivi e coprono tutti i casi.
Con n = 4 il resto è 0 -> «pari»; con n = 7 il resto è 1 -> «dispari». Anche n = 0 dà resto 0 -> «pari», come corretto.
Risultato: Il programma legge n, valuta n % 2 == 0 e stampa «pari» o «dispari»; testato su 4, 7 e 0 fornisce rispettivamente «pari», «dispari», «pari».
Errori frequenti
Approfondimento
Le forme di iterazione di un linguaggio reale sono, sul piano teorico, interscambiabili: un ciclo «for» su un intervallo è zucchero sintattico per un «while» con inizializzazione, condizione e aggiornamento resi espliciti («i <- 0; while i < n: corpo; i <- i + 1»). I costrutti «break» (esci dal ciclo) e «continue» (salta all'iterazione successiva) sono comodi ma vanno usati con misura: introducono uscite multiple e allontanano dall'ideale «un ingresso, un'uscita» della programmazione strutturata, quindi spesso conviene una condizione scritta meglio. Saper negare correttamente una condizione composta è cruciale e si fonda sulle leggi di De Morgan: la negazione di «A and B» è «(not A) or (not B)», quella di «A or B» è «(not A) and (not B)»; sbagliare questa trasformazione, per esempio invertendo un «and» in un «or», è una tipica fonte di errori logici nei rami di selezione. Sul fronte dei cicli enumerativi, l'errore per scarto di uno (off-by-one) nasce dai confini dell'intervallo: «range(n)» in Python genera 0, 1, ..., n−1 (n valori, ultimo escluso), mentre spesso si vorrebbe includere n. I cicli annidati, infine, moltiplicano i costi: due cicli uno dentro l'altro su n elementi eseguono il corpo interno «n²» volte — prima avvisaglia concreta della complessità quadratica «O(n²)» che analizzeremo negli algoritmi di ordinamento elementari.
Ripasso attivo
Scrivi (in pseudocodice o in Python) un programma che legge un numero intero dalla tastiera e stampa se è «pari» oppure «dispari». Indica con chiarezza la struttura di controllo usata e perché la condizione scelta è corretta.
Richiamo attivo
Ricorda i punti chiave — poi rivela.
Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))
Passaggio dei parametri tra chiamante e funzione
Definisci massimo(a, b) che restituisce il maggiore tra due numeri e usala per stampare il massimo tra 4 e 9.
def massimo(a, b): qui a e b sono i parametri formali, cioè i nomi locali che useremo dentro la funzione.
if a > b: return a; else: return b. La funzione confronta i due valori e restituisce, con return, il maggiore.
m = massimo(4, 9): gli argomenti 4 e 9 (parametri attuali) si associano ad a e b. Poiché 4 > 9 è falso, viene eseguito return b, cioè 9.
Il valore restituito (9) sostituisce l'invocazione, viene assegnato a m e poi stampato con print(m).
Risultato: massimo(4, 9) restituisce 9; il programma stampa 9. Parametri formali: a, b; argomenti (parametri attuali): 4, 9.
Errori frequenti
Approfondimento
L'approfondimento naturale dei sottoprogrammi è la ricorsione: una funzione può richiamare sé stessa su un'istanza più piccola del problema, purché siano definiti un caso base (che arresta la discesa) e un passo ricorsivo (che riconduce il problema a uno più semplice). Il fattoriale ne è l'esempio canonico: «fatt(n) = 1 se n = 0, altrimenti n·fatt(n−1)». Ogni chiamata in sospeso è custodita in un record di attivazione (stack frame) impilato sulla pila delle chiamate (call stack), che conserva parametri, variabili locali e indirizzo di ritorno; quando si raggiunge il caso base la pila si svuota risalendo e componendo i risultati parziali. È chiaro allora perché una ricorsione senza caso base — o che non si avvicina mai ad esso — provochi il traboccamento della pila (stack overflow), l'esatto analogo del ciclo infinito per l'iterazione. Il passaggio dei parametri avviene poi secondo due modalità di fondo: per valore, dove il sottoprogramma riceve una copia dell'argomento e non può modificarne l'originale, e per riferimento, dove riceve un riferimento alla variabile del chiamante e può alterarla — distinzione che spiega molti «effetti collaterali» inattesi (per esempio quando si passa una lista). Le funzioni prive di effetti collaterali, che dipendono solo dai propri parametri e non modificano lo stato esterno, si dicono pure: sono le più facili da testare, riusare e comporre, e sono il cuore del paradigma funzionale.
Ripasso attivo
Definisci una funzione massimo(a, b) che restituisce il maggiore tra due numeri, poi mostra una chiamata che ne stampa il risultato per a = 4 e b = 9. Indica con precisione quali sono i parametri formali e quali gli argomenti.
Richiamo attivo
Ricorda i punti chiave — poi rivela.
Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))
Le tre famiglie di errori e quando emergono
Classifica e giustifica: (1) «if x > 0» senza i due punti finali; (2) calcolo di 10 / k con k = 0; (3) media di N voti calcolata dividendo la somma per N+1.
Manca un elemento richiesto dalla forma del linguaggio (i due punti dopo la condizione dell'if). L'interprete rifiuta il programma prima di eseguirlo: è un errore di SINTASSI.
Il programma è ben scritto e si avvia, ma durante l'esecuzione tenta una divisione per zero, operazione non definita, e si interrompe con un'eccezione: è un errore di ESECUZIONE (run-time).
Il programma è sintatticamente corretto e gira senza interrompersi, ma la formula è sbagliata: dividere per N+1 anziché per N dà un valore errato. Nessuno strumento lo segnala: è un errore LOGICO.
Con un test a risultato noto: per i voti 6 e 8 la media è 7; se il programma divide per 3 invece che per 2 restituisce circa 4.67, valore diverso da 7, e l'errore emerge.
Risultato: (1) errore di sintassi; (2) errore di esecuzione; (3) errore logico, individuabile solo con il testing su un caso a risultato noto.
Errori frequenti
Approfondimento
Il testing si organizza con metodo, non a caso. Nel testing a scatola nera (black-box) i casi si scelgono guardando solo alla specifica (input attesi → output attesi), ignorando il codice interno; nel testing a scatola bianca (white-box) si guarda invece al codice per esercitarne tutti i cammini, misurando la copertura (coverage) raggiunta dai test. Poiché gli input possibili sono in genere infiniti, si usa il partizionamento in classi di equivalenza — si raggruppano gli input che il programma tratta nello stesso modo e se ne prova un rappresentante per classe — affiancato dall'analisi dei valori limite, che prova proprio i confini fra le classi (lo zero, il minimo, il massimo, la lista vuota), dove si annidano gli errori più frequenti. Conviene distinguere tre termini spesso confusi: lo sbaglio umano (error) introduce un difetto (fault) nel codice, che a sua volta può provocare un malfunzionamento (failure) osservabile; il debugging risale dal failure al fault. La programmazione difensiva contrasta i difetti a monte: si inseriscono asserzioni (assert) che verificano invarianti e precondizioni e fanno fallire subito, in modo evidente, il programma quando un'ipotesi è violata, anziché lasciarlo proseguire producendo dati errati. Vale infine, come principio metodologico, il monito di Dijkstra: il testing può rivelare la presenza di errori, mai dimostrarne l'assenza — solo una dimostrazione formale di correttezza potrebbe garantirla.
Ripasso attivo
Per ciascuna di queste situazioni, classifica l'errore (sintassi, esecuzione o logico) e giustifica: (1) manca il carattere due punti dopo «if x > 0»; (2) il programma calcola 10 / k con k che vale 0; (3) per calcolare la media di N voti il programma divide la somma per N+1 invece che per N.
Richiamo attivo
Ricorda i punti chiave — poi rivela.
Fonti: Esame di Stato del secondo ciclo — quadri di riferimento e griglie di valutazione (Ministero dell'Istruzione e del Merito (MIM))
Riferimenti e fonti