Istruzioni Iterative in C++
Le istruzioni iterative, comunemente chiamate cicli, permettono l'esecuzione ripetuta fino a quando una condizione è vera. Le istruzioni while
e for
testano la condizione prima di eseguire il corpo. Il do while
esegue il corpo e poi testa la sua condizione.
- Le istruzioni iterative (cicli) permettono l'esecuzione ripetuta di un'istruzione finché una condizione è vera.
- Le istruzioni
while
efor
testano la condizione prima di eseguire il corpo del ciclo. - L'istruzione
do while
esegue il corpo del ciclo almeno una volta prima di testare la condizione. - Il range
for
è un modo semplice per iterare attraverso gli elementi di una sequenza.
L'Istruzione while
Un'istruzione while
esegue ripetutamente un'istruzione target finché una condizione è vera. La sua forma sintattica è
while (condizione)
istruzione
In un while
, istruzione
(che spesso è un blocco) viene eseguita finché condizione
viene valutata come vera. condizione
non può essere vuota. Se la prima valutazione di condizione
produce falso, istruzione
non viene eseguita.
La condizione può essere un'espressione o una dichiarazione di variabile inizializzata. Normalmente, la condizione stessa o il corpo del ciclo devono fare qualcosa per cambiare il valore dell'espressione. Altrimenti, il ciclo potrebbe non terminare mai.
Le variabili definite in una condizione while
o nel corpo while
vengono create e distrutte ad ogni iterazione.
Utilizzo di un Ciclo while
Un ciclo while
è generalmente usato quando vogliamo iterare indefinitamente, come ad esempio quando leggiamo l'input. Un while
è anche utile quando vogliamo accedere al valore della variabile di controllo del ciclo dopo che il ciclo termina. Ad esempio:
vector<int> v;
int i;
// leggi fino a fine-file o altro errore di input
while (cin >> i)
v.push_back(i);
// trova il primo elemento negativo
auto inizio = v.begin();
while (inizio != v.end() && *inizio >= 0)
++inizio;
if (inizio == v.end())
// sappiamo che tutti gli elementi in v sono maggiori o uguali a zero
Il primo ciclo legge dati dall'input standard. Non abbiamo idea di quante volte questo ciclo verrà eseguito. La condizione fallisce quando cin
legge dati non validi, incontra qualche altro errore di input, o raggiunge la fine del file. Il secondo ciclo continua finché non troviamo un valore negativo. Quando il ciclo termina, inizio
è uguale a v.end()
, oppure denota un elemento in v
il cui valore è minore di zero. Possiamo usare lo stato di inizio
al di fuori del while
per determinare l'ulteriore elaborazione.
L'Istruzione for
Tradizionale
La forma sintattica dell'istruzione for
è:
for (istruzione_iniziale; condizione; espressione)
istruzione
Il for
e la parte dentro le parentesi è spesso chiamata intestazione del for.
istruzione_iniziale
deve essere un'istruzione di dichiarazione, un'istruzione di espressione, o un'istruzione nulla. Ciascuna di queste istruzioni termina con un punto e virgola, quindi la forma sintattica può anche essere pensata come
for (inizializzatore; condizione; espressione)
istruzione
In generale, istruzione_iniziale
è usata per inizializzare o assegnare un valore iniziale che viene modificato nel corso del ciclo. condizione
serve come controllo del ciclo. Finché condizione
viene valutata come vera, istruzione
viene eseguita. Se la prima valutazione di condizione
produce falso, istruzione
non viene eseguita. espressione
di solito modifica la/le variabile/i inizializzata/e in istruzione_iniziale
e testata in condizione
. espressione
viene valutata dopo ogni iterazione del ciclo. Come al solito, istruzione
può essere una singola istruzione o un'istruzione composta.
Flusso di Esecuzione in un Ciclo for
Tradizionale
Dato il seguente ciclo for
:
// processa i caratteri in s fino a quando esauriamo i caratteri o troviamo uno spazio
for (decltype(s.size()) indice = 0;
indice != s.size() && !isspace(s[indice]); ++indice)
s[indice] = toupper(s[indice]); // metti in maiuscolo il carattere corrente
l'ordine di valutazione è il seguente:
istruzione_iniziale
viene eseguita una volta all'inizio del ciclo. In questo esempio,indice
viene definito e inizializzato a zero.- Successivamente,
condizione
viene valutata. Seindice
non è uguale as.size()
e il carattere as[indice]
non è uno spazio, il corpo delfor
viene eseguito. Altrimenti, il ciclo termina. Se la condizione è falsa alla prima iterazione, allora il corpo delfor
non viene eseguito affatto. - Se la condizione è vera, il corpo del
for
viene eseguito. In questo caso, il corpo delfor
mette in maiuscolo il carattere as[indice]
. - Infine,
espressione
viene valutata. In questo esempio,indice
viene incrementato di 1.
Questi quattro passaggi rappresentano la prima iterazione del ciclo for
. Il passaggio 1 viene eseguito solo una volta all'ingresso nel ciclo. I passaggi 2, 3 e 4 vengono ripetuti fino a quando la condizione viene valutata come falsa—cioè, quando incontriamo un carattere di spazio in s
, o indice
è maggiore di s.size()
.
Vale la pena ricordare che la visibilità di qualsiasi oggetto definito nell'intestazione del for
è limitata al corpo del ciclo for
. Quindi, in questo esempio, indice
è inaccessibile dopo che il for
si completa.
Definizioni Multiple nell'Intestazione del for
Come in qualsiasi altra dichiarazione, istruzione_iniziale
può definire diversi oggetti. Tuttavia, istruzione_iniziale
può essere solo una singola istruzione di dichiarazione. Pertanto, tutte le variabili devono avere lo stesso tipo base. Come esempio, potremmo scrivere un ciclo per duplicare gli elementi di un vettore alla fine come segue:
// ricorda la dimensione di v e fermati quando arriviamo all'ultimo elemento originale
for (decltype(v.size()) i = 0, dim = v.size(); i != dim; ++i)
v.push_back(v[i]);
In questo ciclo definiamo sia l'indice, i
, che il controllo del ciclo, dim
, in istruzione_iniziale
.
Omissione di Parti dell'Intestazione del for
Un'intestazione for
può omettere qualsiasi (o tutte) parti di istruzione_iniziale
, condizione
, o espressione
.
Possiamo usare un'istruzione nulla per istruzione_iniziale
quando un'inizializzazione non è necessaria. Ad esempio, potremmo riscrivere il ciclo che cercava il primo numero negativo in un vettore in modo che usi un for
:
auto inizio = v.begin();
for ( /* null */; inizio != v.end() && *inizio >= 0; ++inizio)
; // nessun lavoro da fare
Nota che il punto e virgola è necessario per indicare l'assenza di istruzione_iniziale
. Più precisamente, il punto e virgola rappresenta un'istruzione_iniziale
nulla. In questo ciclo, anche il corpo del for
è vuoto perché tutto il lavoro del ciclo è fatto dentro la condizione e l'espressione del for
. La condizione decide quando è il momento di smettere di cercare e l'espressione incrementa l'iteratore.
Omettere condizione
è equivalente a scrivere true
come condizione. Poiché la condizione viene sempre valutata come vera, il corpo del for
deve contenere un'istruzione che esce dal ciclo. Altrimenti il ciclo verrà eseguito indefinitamente:
for (int i = 0; /* nessuna condizione */ ; ++i) {
// processa i; il codice dentro il ciclo deve fermare l'iterazione!
}
Possiamo anche omettere espressione
dall'intestazione del for
. In tali cicli, o la condizione o il corpo deve fare qualcosa per far avanzare l'iterazione. Come esempio, riscriveremo il ciclo while
che leggeva l'input in un vettore di int
:
vector<int> v;
for (int i; cin >> i; /* nessuna espressione */ )
v.push_back(i);
In questo ciclo non c'è bisogno di un'espressione perché la condizione cambia il valore di i
. La condizione testa il flusso di input in modo che il ciclo termini quando abbiamo letto tutto l'input o incontriamo un errore di input.
Esercizi
-
Spiega ciascuno dei seguenti cicli. Correggi eventuali problemi che rilevi.
-
Ciclo 1:
for (int ix = 0; ix != dim; ++ix) { /* ... */ } if (ix != dim) // ...
Risposta: Il ciclo è corretto, ma la variabile
ix
non è accessibile dopo il ciclo perché è stata definita nell'intestazione delfor
. Per correggere questo problema,ix
dovrebbe essere definito prima del ciclo. -
Ciclo 2:
int ix = 0; for (; ix != dim; ++ix) { /* ... */ }
Risposta: Questo ciclo è corretto. La variabile
ix
è definita prima del ciclo, quindi è accessibile dopo il ciclo. -
Ciclo 3:
for (int ix = 0; ix != dim; ++ix, ++dim) { /* ... */ }
Risposta: Questo ciclo è problematico perché
dim
viene incrementato ad ogni iterazione, il che potrebbe causare un ciclo infinito sedim
è inizialmente maggiore diix
. Per correggere questo,dim
non dovrebbe essere modificato all'interno dell'espressione del ciclo.
-
-
Dati due vettori di
int
, scrivi un programma per determinare se un vettore è un prefisso dell'altro. Per vettori di lunghezza disuguale, confronta il numero di elementi del vettore più piccolo. Ad esempio, dati i vettori contenenti 0, 1, 1, e 2 e 0, 1, 1, 2, 3, 5, 8, rispettivamente, il tuo programma dovrebbe restituiretrue
.*Risposta:
#include <iostream> #include <vector> int main() { std::vector<int> v1 = {0, 1, 1, 2}; std::vector<int> v2 = {0, 1, 1, 2, 3, 5, 8}; size_t minSize = std::min(v1.size(), v2.size()); bool isPrefix = true; for (size_t i = 0; i < minSize; ++i) { if (v1[i] != v2[i]) { isPrefix = false; break; } } std::cout << (isPrefix ? "true" : "false") << std::endl; return 0; }
In questo programma abbiamo usato
std::min
per determinare la lunghezza del vettore più piccolo e poi confrontato gli elementi fino a quella lunghezza.
L'Istruzione for
a Intervallo (Range for
)
Lo standard C++11 ha introdotto un'istruzione for
più semplice che può essere usata per iterare attraverso gli elementi di un contenitore o altra sequenza. La forma sintattica dell'istruzione for
a intervallo è:
for (dichiarazione : espressione)
istruzione
espressione
deve rappresentare una sequenza, come una lista di inizializzatori tra parentesi graffe, un array, o un oggetto di un tipo come vector
o string
che ha membri begin
ed end
che restituiscono iteratori.
dichiarazione
definisce una variabile. Deve essere possibile convertire ogni elemento della sequenza al tipo della variabile. Il modo più semplice per assicurarsi che i tipi corrispondano è usare lo specificatore di tipo auto
. In questo modo il compilatore dedurrà il tipo per noi. Se vogliamo scrivere sugli elementi della sequenza, la variabile di ciclo deve essere un tipo riferimento.
Ad ogni iterazione, la variabile di controllo viene definita e inizializzata dal valore successivo nella sequenza, dopodiché istruzione
viene eseguita. Come al solito, istruzione
può essere una singola istruzione o un blocco. L'esecuzione termina una volta che tutti gli elementi sono stati processati.
Abbiamo già visto diversi di questi cicli, ma per completezza, eccone uno che raddoppia il valore di ogni elemento in un vettore:
vector<int> v = {0,1,2,3,4,5,6,7,8,9};
// la variabile di intervallo deve essere un riferimento
// così possiamo scrivere sugli elementi
for (auto &r : v) // per ogni elemento in v
r *= 2; // raddoppia il valore di ogni elemento in v
L'intestazione del for
dichiara la variabile di controllo del ciclo, r
, e la associa a v
. Usiamo auto
per lasciare che il compilatore deduca il tipo corretto per r
. Poiché vogliamo cambiare il valore degli elementi in v
, dichiariamo r
come riferimento. Quando assegniamo a r
dentro il ciclo, quell'assegnazione cambia l'elemento a cui r
è legato.
Un for
a intervallo è definito in termini del for
tradizionale equivalente:
for (auto inizio = v.begin(), fine = v.end(); inizio != fine; ++inizio) {
// r deve essere un riferimento così possiamo cambiare l'elemento
auto &r = *inizio;
// raddoppia il valore di ogni elemento in v
r *= 2;
}
Ora che sappiamo come funziona un for
a intervallo, possiamo capire perché abbiamo detto che non possiamo usare un for
a intervallo per aggiungere elementi a un vettore (o altro contenitore). In un for
a intervallo, il valore di end()
viene memorizzato in cache. Se aggiungiamo elementi a (o li rimuoviamo da) la sequenza, il valore di end
potrebbe essere invalidato. Avremo più da dire su queste questioni in seguito.
L'Istruzione do
while
Un'istruzione do
while
è come un while
ma la condizione viene testata dopo che il corpo dell'istruzione si completa. Indipendentemente dal valore della condizione, eseguiamo il ciclo almeno una volta. La forma sintattica è la seguente:
do
istruzione
while (condizione);
Un do while
termina con un punto e virgola dopo la condizione tra parentesi.
In un do
, istruzione
viene eseguita prima che condizione
venga valutata. condizione
non può essere vuota. Se condizione
viene valutata come falsa, allora il ciclo termina; altrimenti, il ciclo viene ripetuto. Le variabili usate in condizione
devono essere definite al di fuori del corpo dell'istruzione do while
.
Possiamo scrivere un programma che (indefinitamente) fa somme usando un do while
:
// chiedi ripetutamente all'utente una coppia di numeri da sommare
string risp; // usata nella condizione; non può essere definita dentro il do
do {
cout << "per favore inserisci due valori: ";
int val1 = 0, val2 = 0;
cin >> val1 >> val2;
cout << "La somma di " << val1 << " e " << val2
<< " = " << val1 + val2 << "\n\n"
<< "Ancora? Inserisci si o no: ";
cin >> risp;
} while (!risp.empty() && risp[0] != 'n');
Il ciclo inizia chiedendo all'utente due numeri. Poi stampa la loro somma e chiede se l'utente desidera fare un'altra somma. La condizione controlla che l'utente abbia dato una risposta. Se no, o se l'input inizia con una 'n'
, il ciclo viene terminato. Altrimenti il ciclo viene ripetuto.
Poiché la condizione non viene valutata fino a dopo che l'istruzione o il blocco viene eseguito, il ciclo do while
non permette definizioni di variabili dentro la condizione:
do {
// ...
borbotta(pippo);
} while (int pippo = ottieni_pippo()); // errore: dichiarazione in una condizione do
Se potessimo definire variabili nella condizione, allora qualsiasi uso della variabile avverrebbe prima che la variabile venisse definita!
Esercizi
-
Spiega ciascuno dei seguenti cicli. Correggi eventuali problemi che rilevi.
-
Ciclo 1:
do int v1, v2; cout << "Per favore inserisci due numeri da sommare:" ; if (cin >> v1 >> v2) cout << "La somma è: " << v1 + v2 << endl; while (cin);
Risposta: Il ciclo è problematico perché la variabile
v1
ev2
sono definite all'interno del ciclo ma non sono racchiuse in un blocco{}
. Questo significa che solo la prima istruzione dopodo
è considerata parte del ciclo. Per correggere questo, dobbiamo racchiudere il corpo del ciclo in un blocco:do { int v1, v2; cout << "Per favore inserisci due numeri da sommare:" ; if (cin >> v1 >> v2) cout << "La somma è: " << v1 + v2 << endl; } while (cin);
-
Ciclo 2:
do { // ... } while (int val_int = ottieni_risposta());
Risposta: Questo ciclo è problematico perché la variabile
val_int
è definita nella condizione delwhile
, il che non è permesso. Per correggere questo, dobbiamo definireval_int
prima del ciclo:int val_int; do { // ... } while (val_int = ottieni_risposta());
-
Ciclo 3:
do { int val_int = ottieni_risposta(); } while (val_int);
Risposta: Questo ciclo è corretto. La variabile
val_int
è definita all'interno del corpo del ciclo e viene usata nella condizione delwhile
. Tuttavia, poichéval_int
viene ridefinita ad ogni iterazione, il ciclo funzionerà come previsto.
-
-
Scrivi un programma che usa un ciclo
do while
per richiedere ripetutamente due stringhe all'utente e riporta quale stringa è minore dell'altra.Risposta:
#include <iostream> #include <string> using namespace std; int main() { string str1, str2; do { cout << "Inserisci la prima stringa: "; cin >> str1; cout << "Inserisci la seconda stringa: "; cin >> str2; if (str1 < str2) { cout << "La stringa minore è: " << str1 << endl; } else { cout << "La stringa minore è: " << str2 << endl; } } while (!str1.empty() && !str2.empty()); return 0; }