Introduzione al Flusso di Controllo in C++
- Il flusso di controllo determina l'ordine in cui le istruzioni di un programma vengono eseguite.
- Le istruzioni condizionali, come
if
, permettono di eseguire diverse sezioni di codice in base a condizioni specifiche. - I cicli, come
while
efor
, permettono di eseguire ripetutamente una sezione di codice finché una condizione è vera. - La corretta indentazione e formattazione del codice migliorano la leggibilità e la manutenzione del programma.
In C++, le istruzioni normalmente si eseguono sequenzialmente: La prima istruzione in un blocco viene eseguita per prima, seguita dalla seconda, e così via. Naturalmente, pochi programmi, possono essere scritti usando solo l'esecuzione sequenziale. Invece, i linguaggi di programmazione forniscono varie istruzioni di flusso di controllo che permettono percorsi di esecuzione più complicati.
In questa lezione, vedremo una panoramica dei modi con cui il C++ permette di controllare il flusso di esecuzione, attraverso le istruzioni condizionali e i cicli. Vedremo anche come il compilatore può aiutarci a trovare errori nei nostri programmi.
Ritorneremo su questi concetti in lezioni successive. Per ora, ci concentreremo sui concetti di base.
L'istruzione while
Un'istruzione while
esegue ripetutamente una sezione di codice finché una data condizione è vera. Possiamo usare un while
per scrivere un programma che somma i numeri da 1 a 10, estremi inclusi, come segue:
#include <iostream>
int main()
{
int somma = 0, valore = 1;
// continua ad eseguire il while
// finché valore è minore o uguale a 10
while (valore <= 10) {
somma += valore; // assegna somma + valore a somma
++valore; // aggiungi 1 a valore
}
std::cout << "La somma da 1 a 10 inclusi è "
<< somma << std::endl;
return 0;
}
Quando compiliamo ed eseguiamo questo programma, vedremo a schermo:
La somma da 1 a 10 inclusi è 55
Come prima cosa, iniziamo includendo l'intestazione iostream
e definendo main
. All'interno di main
definiamo due variabili int
: somma
, che conterrà la nostra somma, e valore
, che rappresenterà ciascuno dei valori da 1 a 10. Diamo a somma
un valore iniziale di 0 e iniziamo valore con il valore 1.
La nuova parte di questo programma è l'istruzione while
. Un while
ha la forma che segue:
while (condizione)
istruzione;
Un while
si esegue testando alternativamente la condizione
ed eseguendo l'istruzione
associata fino a quando la condizione
è falsa. Una condizione è un'espressione che produce un risultato che è vero o falso. Finché la condizione è vera, l'istruzione viene eseguita. Dopo aver eseguito l'istruzione, la condizione viene testata di nuovo. Se la condizione è di nuovo vera, allora l'istruzione viene di nuovo eseguita. Il while continua, testando alternativamente la condizione ed eseguendo l'istruzione fino a quando la condizione è falsa.
In questo programma, l'istruzione while è
// continua ad eseguire il while finché valore è minore o uguale a 10
while (valore <= 10) {
somma += valore; // assegna somma + valore a somma
++valore; // aggiungi 1 a valore
}
La condizione utilizza l'operatore minore-o-uguale (l'operatore <=
) per confrontare il valore attuale di valore e 10. Finché valore è minore o uguale a 10, la condizione è vera. Se la condizione è vera, eseguiamo il corpo del while. In questo caso, quel corpo è un blocco con due istruzioni:
{
somma += valore; // assegna somma + valore a somma
++valore; // aggiungi 1 a valore
}
Un blocco è una sequenza di zero o più istruzioni racchiuse tra parentesi graffe. Un blocco è un'istruzione e può essere utilizzato ovunque sia richiesta un'istruzione. La prima istruzione in questo blocco utilizza l'operatore di assegnazione composta (l'operatore +=
). Questo operatore aggiunge il suo operando destro al suo operando sinistro e memorizza il risultato nell'operando sinistro. Ha essenzialmente lo stesso effetto di scrivere un'addizione e un'assegnazione:
somma = somma + valore; // *assegna* somma + valore *a* somma
Quindi, la prima istruzione nel blocco aggiunge il valore di valore
all'attuale valore di somma
e memorizza il risultato di nuovo in somma
.
L'istruzione successiva
++valore; // *aggiungi* 1 *a* valore
usa l'operatore di incremento prefisso (l'operatore ++
). L'operatore di incremento aggiunge 1 al suo operando. Scrivere ++valore
è lo stesso che scrivere valore = valore + 1
.
Dopo aver eseguito il corpo del while, il ciclo valuta di nuovo la condizione. Se il valore di valore
(ora incrementato) è ancora minore o uguale a 10, allora il corpo del while viene eseguito di nuovo. Il ciclo continua, testando la condizione ed eseguendo il corpo, fino a quando valore
non è più piccolo o uguale a 10.
Una volta che valore
è maggiore di 10, il programma esce dal ciclo while e continua l'esecuzione con l'istruzione successiva al while. In questo caso, quell'istruzione stampa il nostro output, seguita dal return, che completa il nostro programma principale.
Ritorneremo sui concetti di questa sezione nelle prossime lezioni. Per ora, si noti che il flusso di esecuzione di un programma non è sempre sequenziale. In questo esempio, l'istruzione while ha causato l'esecuzione ripetuta di un blocco di codice.
Esercizi
-
Scrivi un programma che usa un while per sommare i numeri da 50 a 100:
Soluzione:
#include <iostream> int main() { int somma = 0, valore = 50; // somma i valori da 50 a 100 inclusi while (valore <= 100) { somma += valore; // equivalente a somma = somma + valore ++valore; // aggiungi 1 a valore } std::cout << "La somma da 50 a 100 inclusi è " << somma << std::endl; return 0; }
-
Oltre all'operatore
++
che aggiunge 1 al suo operando, c'è un operatore di decremento (--
) che sottrae 1. Usa l'operatore di decremento per scrivere un while che stampa i numeri da dieci a zero.Soluzione:
#include <iostream> int main() { int valore = 10; // stampa i valori da 10 a 0 inclusi while (valore >= 0) { std::cout << valore << std::endl; --valore; // equivalente a valore = valore - 1 } return 0; }
-
Scrivi un programma che chiede all'utente due interi. Stampa ogni numero nell'intervallo specificato da questi due interi.
Soluzione:
#include <iostream> int main() { int v1 = 0, v2 = 0; std::cout << "Enter two integers:" << std::endl; std::cin >> v1 >> v2; // leggere l'input // stampa i valori nell'intervallo da v1 a v2 inclusi while (v1 <= v2) { std::cout << v1 << std::endl; ++v1; // equivalente a v1 = v1 + 1 } return 0; }
L'istruzione for
Nel nostro ciclo while
abbiamo usato la variabile valore per controllare quante volte abbiamo eseguito il ciclo. Abbiamo testato il valore di valore nella condizione e incrementato valore nel corpo del while
.
Questo schema, ossia usare una variabile in una condizione e incrementare quella variabile nel corpo, si verifica così spesso che il linguaggio definisce un secondo costrutto, l'istruzione for
, che abbrevia il codice che segue questo schema. Possiamo riscrivere questo programma usando un ciclo for
per sommare i numeri da 1 a 10 come segue:
#include <iostream>
int main()
{
int somma = 0;
// somma i valori da 1 a 10 inclusi
for (int valore = 1; valore <= 10; ++valore)
somma += valore; // equivalente a somma = somma + valore
std::cout << "La somma da 1 a 10 inclusi è "
<< somma << std::endl;
return 0;
}
Come prima, definiamo somma
e la inizializziamo a zero. In questa versione, definiamo valore
come parte dell'istruzione for
stessa:
for (int valore = 1; valore <= 10; ++valore)
somma += valore;
Ogni istruzione for
ha due parti: un'intestazione e un corpo. L'intestazione controlla quanto spesso viene eseguito il corpo. L'intestazione stessa è composta da tre parti: un istruzione-di-inizializzazione, una condizione, e un'espressione. In questo caso, l'istruzione-di-inizializzazione
int valore = 1;
definisce un oggetto int
chiamato valore
e gli dà un valore iniziale di 1. La variabile valore
esiste solo all'interno del for
; non è possibile utilizzare valore
dopo che questo ciclo termina. L'istruzione-di-inizializzazione viene eseguita solo una volta, all'ingresso nel for
. La condizione
valore <= 10
confronta il valore attuale in valore
con 10. La condizione viene testata ogni volta che si attraversa il ciclo. Finché valore
è minore o uguale a 10, eseguiamo il corpo del for. L'espressione viene eseguita dopo il corpo del for. Qui, l'espressione
++valore
usa l'operatore di incremento prefisso, che aggiunge 1 al valore di valore
. Dopo aver eseguito l'espressione, il for ritesta la condizione. Se il nuovo valore di valore
è ancora minore o uguale a 10, allora il corpo del ciclo for viene eseguito di nuovo. Dopo aver eseguito il corpo, valore
viene incrementato di nuovo. Il ciclo continua fino a quando la condizione fallisce.
In questo ciclo, il corpo del for esegue la somma
somma += valore; // equivalente a somma = somma + valore
Per riassumere, il flusso di esecuzione complessivo di questo for è:
- Crea
valore
e inizializzalo a 1. - Controlla se
valore
è minore o uguale a 10. Se il test ha successo, esegui il corpo del for. Se il test fallisce, esci dal ciclo e continua l'esecuzione con la prima istruzione dopo il corpo del for. - Incrementa
valore
. - Ripeti il test nel passo 2, continuando con i passi rimanenti finché la condizione è vera.
Esercizi sul for
-
Cosa fa il seguente ciclo for? Qual è il valore finale di somma?
int somma = 0; for (int i = -100; i <= 100; ++i) somma += i;
Questo ciclo somma tutti i numeri interi da -100 a 100. Il valore finale di
somma
sarà 0, poiché i numeri negativi e positivi si annullano a vicenda. -
Scrivi un programma che usa un
for
per sommare i numeri da 50 a 100:Soluzione:
#include <iostream> int main() { int somma = 0; // somma i valori da 50 a 100 inclusi for (int valore = 50; valore <= 100; ++valore) somma += valore; // equivalente a somma = somma + valore std::cout << "La somma da 50 a 100 inclusi è " << somma << std::endl; return 0; }
Lettura di un numero sconosciuto di valori
Nelle sezioni precedenti, abbiamo scritto programmi che sommavano i numeri da 1 a 10. Un'estensione logica di questo programma sarebbe chiedere all'utente di inserire un insieme di numeri da sommare. In questo caso, non sapremo quanti numeri aggiungere. Invece, continueremo a leggere numeri fino a quando non ce ne saranno più da leggere:
#include <iostream>
int main()
{
int somma = 0, valore = 0;
// leggi fino alla fine della riga
// calcolando un totale corrente di tutti i valori letti
while (std::cin >> valore)
somma += valore; // equivalente a somma = somma + valore
std::cout << "La somma è: " << somma << std::endl;
return 0;
}
Se diamo a questo programma l'input
3 4 5 6
allora il nostro output sarà
La somma è: 18
La prima riga all'interno di main
definisce due variabili int
, chiamate somma
e valore
, che inizializziamo a 0. Useremo valore
per contenere ogni numero mentre lo leggiamo dall'input. Leggiamo i dati all'interno della condizione del while
:
while (std::cin >> valore)
Valutare la condizione del while
esegue l'espressione
std::cin >> valore
Quell'espressione legge il numero successivo dall'input standard e memorizza quel numero in valore. L'operatore di input restituisce il suo operando sinistro, che in questo caso è std::cin
. Questa condizione, quindi, testa std::cin
.
Quando usiamo un istream
come condizione, l'effetto è testare lo stato del flusso. Se il flusso è valido, cioè, se il flusso non ha incontrato un errore, allora il test ha successo. Un istream
diventa non valido quando raggiungiamo la fine del file o incontriamo un input non valido, come leggere un valore che non è un intero. Un istream
che è in uno stato non valido farà sì che la condizione restituisca falso.
Pertanto, il nostro while si esegue fino a quando non incontriamo la fine del file (o un errore di input). Il corpo del while utilizza l'operatore di assegnazione composta per aggiungere il valore attuale alla somma in evoluzione. Una volta che la condizione fallisce, il while termina. Quindi viene eseguita l'istruzione successiva, che stampa la somma seguita da endl
.
Inserimento della fine del file da tastiera
Quando inseriamo input a un programma dalla tastiera, i diversi sistemi operativi usano convenzioni diverse per permetterci di indicare la fine del file.
Nei sistemi Windows inseriamo una fine del file digitando CTRL+Z seguito dal tasto Invio o Ritorno. Nei sistemi UNIX, compresi Mac OS X, la fine del file è di solito CTRL+D.
Compilazione Rivisitata
Parte del lavoro del compilatore è cercare errori nel testo del programma. Un compilatore non può rilevare se un programma fa ciò che il suo autore intende, ma può rilevare errori nella forma del programma. I seguenti sono i tipi più comuni di errori che un compilatore può rilevare.
-
Errori di sintassi: Il programmatore ha commesso un errore grammaticale nel linguaggio C++. Il seguente programma illustra errori di sintassi comuni; ogni commento descrive l'errore sulla riga seguente:
// errore: manca ) nella lista dei parametri per main int main ( { // errore: usato due punti, non un punto e virgola, dopo endl std::cout << "Leggi ogni file." << std::endl: // errore: mancano virgolette attorno al letterale di stringa std::cout << Aggiorna master. << std::endl; // errore: il secondo operatore di output è mancante std::cout << "Scrivi nuovo master." std::endl; // errore: manca ; nella dichiarazione di ritorno return 0 }
-
Errori di tipo: Ogni elemento di dati in C++ ha un tipo associato. Il valore 10, ad esempio, ha un tipo di
int
(o, più colloquialmente, "è unint
"). La parola "ciao", comprese le virgolette doppie, è un letterale di stringa. Un esempio di errore di tipo è passare un letterale di stringa a una funzione che si aspetta un argomentoint
. -
Errori di dichiarazione: Ogni nome usato in un programma C++ deve essere dichiarato prima di essere usato. La mancata dichiarazione di un nome di solito comporta un messaggio di errore. I due errori di dichiarazione più comuni sono dimenticare di usare
std::
per un nome della libreria e scrivere male il nome di un identificatore:#include <iostream> int main() { int v1 = 0, v2 = 0; std::cin >> v >> v2; // errore: usa "v" non "v1" // errore: cout non definito; dovrebbe essere std::cout cout << v1 + v2 << std::endl; return 0; }
I messaggi di errore di solito contengono un numero di riga e una breve descrizione di ciò che il compilatore crede che abbiamo fatto di sbagliato. È una buona pratica correggere gli errori nella sequenza in cui vengono segnalati. Spesso un singolo errore può avere un effetto a cascata e causare al compilatore di segnalare più errori di quelli che sono effettivamente presenti. È anche una buona idea ricompilare il codice dopo ogni correzione o dopo aver apportato al massimo un piccolo numero di correzioni ovvie. Questo ciclo è noto come modifica-compila-debug.
L'istruzione if
Come la maggior parte dei linguaggi, C++ fornisce un'istruzione if
che supporta l'esecuzione condizionale. Possiamo usare un if
per scrivere un programma per contare quante volte consecutive appare ogni valore distinto nell'input:
#include <iostream>
int main()
{
// valoreCorrente è il numero che stiamo contando;
// leggeremo nuovi valori in valore
int valoreCorrente = 0, valore = 0;
// leggi il primo numero e assicurati di avere dati da elaborare
if (std::cin >> valoreCorrente) {
// memorizza il conteggio per il valore corrente che stiamo elaborando
int cnt = 1;
// leggi i numeri rimanenti
while (std::cin >> valore) {
// se i valori sono uguali
if (valore == valoreCorrente)
++cnt; // aggiungi 1 a cnt
else {
// altrimenti, stampa il conteggio per il valore precedente
std::cout << valoreCorrente << " si verifica "
<< cnt << " volte" << std::endl;
valoreCorrente = valore; // ricorda il nuovo valore
cnt = 1; // ripristina il contatore
}
} // il ciclo while termina qui
// ricorda di stampare il conteggio per l'ultimo valore nel file
std::cout << valoreCorrente << " si verifica "
<< cnt << " volte" << std::endl;
} // l'istruzione if più esterna termina qui
return 0;
}
Se diamo a questo programma il seguente input:
42 42 42 42 42 55 55 62 100 100 100
allora l'output dovrebbe essere
42 si verifica 5 volte
55 si verifica 2 volte
62 si verifica 1 volta
100 si verifica 3 volte
Gran parte del codice in questo programma dovrebbe essere familiare dai nostri programmi precedenti. Iniziamo definendo valore
e valoreCorrente
: valoreCorrente
terrà traccia di quale numero stiamo contando; valore
conterrà ogni numero mentre lo leggiamo dall'input. Ciò che è nuovo sono le due istruzioni if
. La prima if
if (std::cin >> valoreCorrente) {
assicura che l'input non sia vuoto. Come un while
, un if
valuta una condizione. La condizione nel primo if
legge un valore in valoreCorrente. Se la lettura ha successo, allora la condizione è vera ed eseguiamo il blocco che inizia con la graffa aperta dopo la condizione. Quel blocco termina con la graffa chiusa appena prima dell'istruzione di ritorno.
Una volta che sappiamo che ci sono numeri da contare, definiamo cnt
, che conterà quante volte ogni numero distinto si verifica. Usiamo un ciclo while
simile a quello nella sezione precedente per (ripetutamente) leggere numeri dall'input standard.
Il corpo del while
è un blocco che contiene la seconda istruzione if
:
// se i valori sono uguali
if (valore == valoreCorrente)
++cnt; // aggiungi 1 a cnt
else {
// altrimenti, stampa il conteggio per il valore precedente
std::cout << valoreCorrente << " si verifica "
<< cnt << " volte" << std::endl;
valoreCorrente = valore; // ricorda il nuovo valore
cnt = 1; // ripristina il contatore
}
La condizione in questo if
usa l'operatore di uguaglianza (l'operatore ==
) per testare se valore è uguale a valoreCorrente. Se sì, eseguiamo l'istruzione che segue immediatamente la condizione. Quell'istruzione incrementa cnt
, indicando che abbiamo visto valoreCorrente
ancora una volta.
Se la condizione è falsa, cioè, se valore non è uguale a valoreCorrente
, allora eseguiamo l'istruzione dopo l'else
. Questa istruzione è un blocco costituito da un'istruzione di output e due assegnazioni. L'istruzione di output stampa il conteggio per il valore che abbiamo appena finito di elaborare. Le assegnazioni ripristinano cnt
a 1 e valoreCorrente
a valore
, che è il numero che abbiamo appena letto.
Differenza tra =
e ==
C++ usa =
per l'assegnazione e ==
per l'uguaglianza. Entrambi gli operatori possono apparire all'interno di una condizione. È un errore comune scrivere =
quando si intende ==
all'interno di una condizione.
Esercizi
-
Cosa succede nel programma presentato in questa sezione se i valori di input sono tutti uguali? E se non ci sono valori duplicati?
Soluzione: Se tutti i valori di input sono uguali, il programma stamperà una sola riga con il conteggio totale di quel valore. Se non ci sono valori duplicati, il programma stamperà una riga per ogni valore, ciascuna con un conteggio di 1.
Indentazione e Formattazione
I programmi C++ sono per lo più a formato libero, il che significa che i posti dove mettiamo le parentesi graffe, l'indentazione, i commenti e le nuove righe di solito non hanno effetto su ciò che i nostri programmi significano. Ad esempio, la parentesi graffa che denota l'inizio del corpo di main
potrebbe essere sulla stessa riga di main
; posizionata come abbiamo fatto, all'inizio della riga successiva; o collocata ovunque ci piaccia. L'unico requisito è che la graffa aperta deve essere il primo carattere non vuoto e non commentato dopo la lista dei parametri di main
.
Sebbene siamo per lo più liberi di formattare i programmi come desideriamo, le scelte che facciamo influenzano la leggibilità dei nostri programmi. Potremmo, ad esempio, aver scritto main
su un'unica lunga riga. Tale definizione, sebbene legale, sarebbe difficile da leggere.
Si svolgono dibattiti senza fine su quale sia il modo giusto di formattare i programmi C o C++. La nostra convinzione è che non ci sia uno stile corretto unico, ma che ci sia valore nella coerenza. La maggior parte dei programmatori indenta le parti secondarie dei loro programmi, come abbiamo fatto con le istruzioni all'interno di main e i corpi dei nostri cicli. Tendiamo a mettere le parentesi graffe che delimitano le funzioni sulle loro righe. Indentiamo anche le espressioni IO composte in modo che gli operatori si allineino. Altre convenzioni di indentazione diventeranno chiare man mano che i nostri programmi diventeranno più sofisticati.
L'importante è tenere a mente che sono possibili altri modi di formattare i programmi. Quando scegli uno stile di formattazione, pensa a come influisce sulla leggibilità e sulla comprensione. Una volta scelto uno stile, usalo in modo coerente.