Riepilogo
Questa lezione apre la prima unità del corso, dedicata ai primi passi nella programmazione con C++. Spiega a che cosa serve l’unità: portare chi non ha mai programmato da un problema enunciato in linguaggio corrente fino a un programma che compila, si esegue, legge dati e scrive risultati, sapendo che cosa ha fatto ciascuno strumento lungo il cammino; presenta le sue sei lezioni e il filo che le unisce, dall’algoritmo scritto in italiano fino alla classificazione degli errori; precisa il poco che si dà per scontato, cioè l’aritmetica scolastica, l’abitudine di seguire istruzioni scritte e la gestione di file e cartelle; formula il problema che l’unità risolve, quello di convertire un calcolo che si sa fare a mano in un procedimento che una macchina esegue senza sbagliare e senza stancarsi; e mostra dove conduce, nelle unità successive e nel corso Programmazione in C++ II.
Obiettivi di apprendimento
- Descrivere la sequenza dei contenuti dell’unità e spiegare che cosa apporta ciascuna lezione alla successiva.
- Riconoscere le conoscenze preliminari che il corso presuppone (operare con interi e decimali, calcolare un quoziente e un resto, seguire istruzioni scritte, salvare un file in una cartella determinata) e ripassarle se occorre.
- Spiegare, con un esempio, il cammino che l’unità insegna a percorrere: dall’enunciato alla specifica, dalla specifica all’algoritmo e dall’algoritmo al programma che si compila e si esegue.
- Riconoscere che cosa ci si aspetta di saper fare al termine dell’unità, e distinguerlo da ciò che si imparerà nelle unità successive.
Valutazione d’ingresso
Prima di percorrere l’unità conviene misurare il punto di partenza. Questa valutazione è un’autovalutazione: non ha voto, e il suo unico scopo è orientare lo studio. Poiché questa è la prima unità del corso, non ci sono domande su unità precedenti; al loro posto, le prime tre verificano ciò che il corso dà per scontato: il quoziente e il resto di una divisione intera, l’ordine dei passi di un procedimento e il nome, l’estensione e la cartella di un file. Le sette successive toccano, in ordine, le idee centrali delle sei lezioni dell’unità. Chi non ha mai programmato non deve aspettarsi di indovinarle, e non importa: ogni risposta, giusta o no, spiega il punto e nomina la lezione che lo tratta. Le stesse abilità si misurano di nuovo alla fine, nella lezione di sintesi.
Prima di iniziare l’unità, misura il tuo punto di partenza. È un’autovalutazione breve: non riceve voto e il risultato resta solo nel tuo browser.
Domanda 1Dalle unità precedentiSi distribuiscono 47 fogli fra 6 persone, in parti intere uguali, e quelli che non bastano per un giro completo si mettono da parte. Quanti ne riceve ciascuna persona e quanti se ne mettono da parte?
È la divisione con i decimali, non quella intera: i fogli non si spezzano, e la domanda chiede quanti ne avanzano.Arrotondare il quoziente per eccesso richiede fogli che non ci sono; il resto della divisione intera non è mai negativo quando dividendo e divisore sono positivi.Confonde il resto con la parte decimale del quoziente: il resto si conta in fogli, non in frazioni di giro.È la divisione intera: il quoziente è il massimo numero di giri completi, e il resto ciò che avanza, che è sempre minore del divisore. Il corso la usa fin dal primo programma, e in C++ ha operatori propri.Ripassare: Dal problema all’algoritmo
Domanda 2Dalle unità precedentiPer inviare un file allegato per posta elettronica ci sono quattro passi: A, scrivere l’indirizzo del destinatario; B, premere «Invia»; C, aprire un messaggio nuovo; D, allegare il file. Qual è l’ordine adeguato?
L’ordine dell’elenco non è l’ordine dell’esecuzione: con esso, il messaggio verrebbe inviato prima di esistere.Il risultato dipende dall’ordine: ciò che si allega dopo l’invio non arriva.Invia il messaggio senza destinatario e senza allegato; i passi successivi non lo cambiano più.Alcuni passi dipendono da altri (non si può allegare a un messaggio che non è stato aperto, né completarne uno già inviato), e altri no. Riconoscere queste dipendenze è la prima cosa che richiede scrivere un algoritmo.Ripassare: Dal problema all’algoritmo
Domanda 3Dalle unità precedentiUn file è salvato in
C:\Users\ana\programas\suma.cpp. Qual è il suo nome, quale la sua estensione e in quale cartella si trova?Confonde la cartella che contiene il file con il file stesso: il nome è l’ultimo componente del percorso.L’estensione fa parte del nome; alcuni sistemi la nascondono per impostazione predefinita, e il corso chiede di mostrarla, perchésuma.cppesuma.exesono file molto diversi.C:è l’unità dove comincia il percorso, non un’estensione.Il percorso elenca le cartelle dalla radice fino al file; l’estensione, ciò che segue l’ultimo punto del nome, dice che genere di contenuto ha il file:.cppè codice sorgente C++. Per compilarlo, il terminale deve trovarsi in quella cartella o ricevere il percorso completo.Ripassare: Compilare, eseguire e configurare
Domanda 4Una ricetta dice: «Se l’impasto è secco, aggiungere un cucchiaio d’acqua e impastare; ripetere finché non lo è più». L’impasto ha bisogno esattamente di tre cucchiai per smettere di essere secco. Quante volte si chiede se l’impasto è secco?
Non conta la domanda la cui risposta è «no», che pure si fa: senza di essa, nessuno saprebbe che bisogna smettere di aggiungere acqua.Confonde il testo del procedimento con la sua esecuzione: la condizione è scritta una volta, ma si valuta a ogni giro.Conta gli intervalli fra i cucchiai, non le domande.In ogni ripetizione che domanda prima di agire, la condizione si valuta una volta in più del corpo: l’ultima domanda è quella che permette di uscire. Seguire il procedimento a mano, contando, lo mostra senza ambiguità.Ripassare: Dal problema all’algoritmo
Domanda 5Quale di queste istruzioni può seguirla chiunque, o una macchina, esattamente nello stesso modo?
Dipende dal gusto di chi esegue: due persone la seguirebbero in modo diverso, e una macchina non ha gusto.Non dice quante volte né che cosa sia venire bene: la condizione di arresto non è verificabile.Non fissa i numeri: «grande» non è una quantità, e il divisore potrebbe essere zero.Dice quale operazione si fa, con quale dato e dove resta il risultato; nulla dipende dal giudizio di chi la esegue. Questa precisione è una delle tre esigenze di un algoritmo.Ripassare: Dal problema all’algoritmo
Domanda 6Un programma compila senza errori né avvisi, si esegue bene con molti dati, e con uno in particolare si arresta con il messaggio «division by zero». In quale momento si è prodotto l’errore?
Il compilatore traduce il testo, non lo esegue, e non conosce i dati che l’utente scriverà.Il linker riunisce le parti del programma; non fa calcoli né vede dati.Con altri dati lo stesso eseguibile funziona: il difetto sta in ciò che il programma fa con quel dato, non negli strumenti.Ciò che dipende solo dal testo del programma si scopre compilando o collegando; ciò che dipende dai dati, solo eseguendo. Per questo un programma che compila non è, per ciò stesso, privo di errori.Ripassare: Programmi, compilatori e C++
Domanda 7Si è installato Visual Studio Code con la sua estensione per C++, e chiedendo
g++ --versionnel terminale, questo risponde che non riconosceg++. Che cosa è più probabile?L’estensione non contiene il compilatore né interviene in ciò che il terminale trova.Il comandog++ --versionnon compila alcun programma: chiede solo la versione, e il terminale non ha nemmeno trovato a chi chiederla.L’editor è indifferente: ciò che manca è il compilatore, in qualsiasi editor.L’editor non compila: chiama un compilatore a parte, che dev’essere installato e trovato dal terminale. L’estensione per C++ aiuta a scrivere il codice, ma non porta con sé un compilatore.Domanda 8Il compilatore scrive un messaggio che comincia con
warning:e, nonostante esso, produce l’eseguibile. Che cosa significa?Questo sarebbe un errore (error:), e allora non ci sarebbe l’eseguibile.Funzionare con i dati testati non dimostra nulla sugli altri; un avviso ignorato è, spesso, l’errore di domani.L’eseguibile è normale; ciò che è dubbio è quello che il programma fa nel punto segnalato.Un errore impedisce di ottenere l’eseguibile; un avviso lo permette, ma segnala codice sospetto. Molti avvisi rivelano difetti reali, e per questo il corso esige programmi che compilino senza alcuno.Ripassare: Compilare, eseguire e configurare
Domanda 9Dove comincia l’esecuzione di un programma C++?
La prima riga è di solito una direttiva#include, che si elabora prima di compilare e non si esegue.L’ordine nel file non decide il punto di ingresso; lo decide il nomemain.Lo standard fissa il nome del punto di ingresso; non si sceglie nel comando di compilazione.mainè il punto di ingresso: il sistema la chiama all’avvio del programma, e il programma termina quando essa termina. Un programma senzamaincompila, ma non si collega.Ripassare: Anatomia di un programma
Domanda 10Un programma dichiara
int age{};, eseguestd::cin >> age;per leggere un’età, e l’utente scriveabc. Che cosa succede?Non si arresta: continua come se la lettura fosse andata bene, e per questo il difetto è pericoloso.Una variabileintconserva solo interi; le lettere non si convertono in numero né si conservano.La lettura non riprova: fallisce una volta e lascia lo stream in errore.La lettura di un intero fallisce quando incontra una lettera; lo standard dice che la variabile riceve 0 e che le letture successive non faranno nulla finché non si tratta l’errore. Se il programma non verifica la lettura, scrive conclusioni false senza alcun segnale.Ripassare: Input, output ed errori
I contenuti dell’unità
L’unità ha sei lezioni, che percorrono in ordine il cammino da un problema a un programma funzionante. Ciascuna presuppone la precedente, e la prima non presuppone nulla del linguaggio.
- Dal problema all’algoritmo. Il corso comincia senza codice, perché l’algoritmo precede il programma. La lezione insegna a specificare un problema mediante il suo input, il suo output e la relazione fra i due (la precondizione e la postcondizione); a descrivere un algoritmo in passi numerati, senza ambiguità; a seguirlo a mano con una tabella di traccia; a riconoscervi la sequenza, la decisione e la ripetizione; e a giustificare che una ripetizione termina, con una quantità che decresce a ogni giro.
- Programmi, compilatori e C++. Un algoritmo scritto per una macchina è un programma, e un programma C++ non si esegue così come si scrive: il preprocessore, il compilatore e il linker lo convertono in un eseguibile. La lezione percorre queste tre fasi, distingue ciò che si scopre compilando da ciò che si manifesta solo eseguendo, confronta il modello compilato con quello interpretato e colloca nel tempo gli standard del linguaggio, fino a C++23, quello che il corso usa, e C++26, la cui implementazione nei compilatori è in corso.
- Installare l’ambiente su Windows, macOS e Linux. Tre guide complete, una per sistema, per installare un compilatore capace di C++23 e l’editor Visual Studio Code con la sua estensione per C++, verificare che tutto funzioni con un programma di prova e risolvere i malfunzionamenti frequenti: il compilatore che il terminale non trova, la versione vecchia e lo standard che non è stato richiesto.
- Compilare, eseguire e configurare. Il ciclo di modificare, salvare, compilare, eseguire e leggere, dal terminale e dall’editor, con il comando del corso. La lezione distingue gli errori, che impediscono l’eseguibile, dagli avvisi, che segnalano codice sospetto; attiva i sanitizer (quello degli errori di memoria, AddressSanitizer, e quello del comportamento indefinito, UndefinedBehaviorSanitizer), che sorvegliano il programma mentre è in esecuzione; e spiega perché un output mostra simboli strani al posto degli accenti e della ñ.
- Anatomia di un programma. Il programma minimo, pezzo per pezzo: la direttiva di inclusione e gli header, la funzione
maine il codice di uscita, il namespacestd, le istruzioni, i blocchi e i commenti. La lezione insegna a correggere gli errori di struttura leggendo i messaggi del compilatore a partire dal primo, e fissa la guida di stile del corso. - Input, output ed errori. Il programma comunica con l’esterno: scrive con
std::printestd::println, con specifiche di formato per la larghezza e i decimali, e legge constd::cin. La lezione mostra che cosa succede quando l’input non ha la forma attesa e come si verifica la lettura, e chiude l’unità con la classificazione degli errori secondo il loro momento: di compilazione, di collegamento, di esecuzione e di logica.
Il filo che le unisce è quello del programma stesso. La prima lezione lo concepisce, la seconda spiega in che cosa si converte, la terza installa gli strumenti che lo convertono, la quarta insegna a usarli e ad ascoltare ciò che dicono, la quinta lo seziona e la sesta lo mette a dialogare con chi lo usa. Se è così, alla fine dell’unità lo studente sa ancora molto poco di C++, ma sa come ci si lavora, ed è questo ciò che il resto del corso presuppone.
Ciò che si dà per scontato
Il corso non presuppone alcuna esperienza precedente di programmazione. Presuppone, invece, tre abilità che conviene ripassare ora, se la valutazione d’ingresso ha mostrato qualche dubbio.
La prima è l’aritmetica scolastica: operare con gli interi e con i decimali, rispettare l’ordine delle operazioni (la moltiplicazione e la divisione prima dell’addizione e della sottrazione, salvo parentesi) e, soprattutto, calcolare il quoziente e il resto di una divisione intera. Quest’ultima è più importante di quanto sembri. Dividere 47 fogli fra 6 persone dà 7 a ciascuna e ne avanzano 5, il che si scrive , con il resto minore del divisore . C++ ha un operatore per il quoziente e un altro per il resto, e gran parte dei calcoli con gli interi (convertire secondi in ore, separare le cifre di un numero, decidere se un numero è pari) si riduce a essi. Chi voglia studiarla con calma la trova, con la sua dimostrazione, nel corso Algebra e Geometria I: Numeri, Funzioni e Geometria del Piano; qui basta saperla calcolare.
La seconda è l’abitudine di seguire alla lettera istruzioni scritte e di riconoscere quando un’istruzione non basta per sapere che cosa fare. Non si tratta di una conoscenza, ma di un atteggiamento: leggere «aggiungere sale a piacere» e accorgersi che quell’istruzione dipende da chi la esegue. La prima lezione dell’unità trasforma quell’atteggiamento in metodo.
La terza è l’uso di base di un computer: creare una cartella, salvarvi un file con un nome scelto, riconoscere l’estensione del nome (.cpp, .txt, .exe) e sapere in quale cartella si trova ciascun file. Conviene configurare fin da ora il sistema perché mostri le estensioni, che alcuni sistemi, come Windows, nascondono per impostazione predefinita: il codice sorgente suma.cpp e l’eseguibile suma.exe sono file molto diversi, e confonderli è una fonte di errori assurdi. L’unità insegna anche a usare il terminale, sicché non occorre saperlo fare in anticipo; basta essere disposti a scrivere comandi invece di premere pulsanti. Installare programmi richiede, infine, di poterlo fare sul proprio computer; la lezione di installazione indica un’alternativa in linea per chi non possa.
Non si presuppone l’inglese. I messaggi del compilatore sono in inglese, e il corso insegna a leggerli campo per campo, in modo che ciò che conta in essi (il file, la riga, il tipo di difetto) si capisca senza padroneggiare la lingua. Né si presuppone alcuna matematica oltre quella scolastica: gli esempi usano la divisione, le medie, le tabelline, le date e le ore.
Il problema che l’unità risolve
Un calcolo che si sa fare a mano non è ancora un programma. La distanza fra i due è ciò che l’unità insegna a percorrere, e un problema semplice permette di misurarla.
Un cronometro registra la durata di ciascuna attività come un numero di secondi, per esempio 7384, e la si vuole scrivere nella forma abituale di ore, minuti e secondi, con i minuti e i secondi sempre su due cifre: 2:03:04. A mano, il calcolo è una catena di divisioni: , da cui risultano 2 ore e 184 secondi; , da cui risultano 3 minuti e 4 secondi. Per una durata si fa in un minuto. Per le mille durate di un registro, invece, il calcolo a mano è lungo e soggetto a errori, e il compito si presta a essere eseguito da una macchina.
Ora, una macchina non può ricevere la spiegazione precedente. Riceve un testo in un linguaggio di programmazione, e per scrivere quel testo bisogna aver risolto prima tre questioni che il calcolo a mano lasciava implicite. La prima è la specifica: che cosa entra (un numero intero di secondi, non negativo), che cosa esce (tre interi, le ore, i minuti e i secondi) e quale relazione li unisce: se è il totale, , con e . La seconda è l’algoritmo: i passi precisi che, per qualsiasi input ammissibile, producono quell’output.
- assume il valore del quoziente di per .
- assume il valore del quoziente per del resto di per .
- assume il valore del resto di per .
- Scrivere , due punti, su due cifre, due punti e su due cifre.
La terza è la scrittura dell’algoritmo in C++, con la forma esatta che il linguaggio esige, e la sua traduzione in un eseguibile con gli strumenti adeguati. Il programma seguente lo fa. Contiene molte cose che non sono ancora state spiegate: le variabili (lezione 2.1), gli operatori / e %, che danno il quoziente e il resto della divisione intera (lezione 3.1), la lettura con std::cin e le specifiche di formato (lezione 1.6); la specifica {:02} di questo programma, che scrive un numero su due posizioni completando con zeri a sinistra, è una variante della larghezza che quella lezione insegna e che la lezione 3.1 userà. Per ora basta riconoscervi i quattro passi dell’algoritmo.
#include <iostream>#include <print>int main(){ int total{}; // le variabili saranno spiegate nella lezione 2.1 std::print("Durata in secondi: "); std::cin >> total; const int hours{total / 3600}; const int minutes{total % 3600 / 60}; const int seconds{total % 60}; std::println("{} s = {}:{:02}:{:02}", total, hours, minutes, seconds);}7384Durata in secondi: 7384 s = 2:03:04Le righe da 9 a 11 sono i passi da 1 a 3, e la riga 12 è il passo 4. Che il risultato coincida con quello calcolato a mano non è un caso, ma la verifica che ogni programma merita: testarlo prima con un dato di cui si conosce la risposta.
Il programma risolve il problema, ma l’unità insegna anche a diffidarne. Se l’utente scrive abc invece di un numero, la lettura fallisce, total resta a zero e il programma scrive, senza alcun segnale d’allarme, che la durata è 0:00:00: una conclusione falsa con l’apparenza di una vera. Se scrive una durata negativa, la specifica non dice che cosa debba succedere, e il risultato è privo di senso. Se si scrive total / 360 invece di total / 3600, il programma compila, si esegue e dà un risultato sbagliato, e nessuno strumento se ne accorge. Ciascuno di questi difetti appartiene a un tipo diverso di errore, che si scopre in un momento diverso e si cerca in un modo diverso; l’ultima lezione dell’unità li classifica. Ne segue che il problema che l’unità risolve non è solo quello di scrivere questo programma, ma quello di sapere perché funziona, con quali dati smette di funzionare e quale strumento avviserà, o no, di ciascun difetto.
Pertanto, al termine dell’unità lo studente potrà scrivere, compilare ed eseguire programmi di questo tipo: pochi dati in input, alcuni calcoli in sequenza e alcuni risultati scritti con formato. Non potrà ancora prendere decisioni né ripetere istruzioni in C++ (anche se sì in un algoritmo scritto in italiano), né scegliere con criterio il tipo di ciascun dato; è ciò che portano le unità successive.
Dove conduce
L’unità è la soglia del corso. Ciò che vi si usa senza spiegarlo si spiega dopo, in quest’ordine. L’unità 2, «Dati e tipi», spiega le variabili e come si conservano in memoria, i limiti degli interi (e perché un calcolo corretto può andare in overflow), l’arrotondamento dei numeri con decimali e le conversioni fra tipi. L’unità 3, «Espressioni e operatori», spiega l’aritmetica di C++, con il quoziente e il resto dei negativi, la logica delle condizioni e ciò che lo standard lascia indefinito in un’espressione. L’unità 4, «Controllo del flusso», porta in C++ le decisioni e le ripetizioni che la prima lezione scrive in italiano, e insegna a giustificare che un ciclo è corretto e termina. L’unità 5 scompone i programmi in funzioni; la 6 introduce i dati composti (array, vettori, tabelle e record), con gli algoritmi di ricerca e di ordinamento e il loro costo; la 7 tratta il testo, l’input robusto e i file; e l’8, i programmi di più file, i test sistematici e il debugger.
Dopo il corso, Programmazione in C++ II prosegue con ciò che questo lascia fuori di proposito: i puntatori e la memoria dinamica, le classi con metodi, i template propri, le funzioni anonime e le eccezioni. Nessuno di questi temi è necessario per risolvere i problemi di questo corso, e rimandarli permette di concentrarsi su ciò che vale in qualsiasi linguaggio: specificare, progettare l’algoritmo, scriverlo con chiarezza e verificare che è corretto. La lezione di sintesi di questa unità raccoglierà proprio questa distinzione, fra ciò che è proprio di C++ e ciò che si porterà in qualsiasi altro linguaggio.