- Programmazione » Programmazione » Guida C - Manuale programmazione con articoli e risorse interessanti
Il flusso elaborativo: for
for
Tra le istruzioni C di controllo dei ciclo, la for
è sicuramente la più versatile ed efficiente.
La for è presente in tutti (o quasi) i
linguaggi, ma in nessuno ha la potenza di cui dispone in C.
Infatti, in generale, i cicli di tipo while e
derivati sono utilizzati nelle situazioni in cui non è
possibile conoscere a priori il numero esatto di iterazioni,
mentre la for, grazie alla sua logica "punto
di partenza; limite; passo d'incremento" , si
presta proprio ai casi in cui si può determinare in
partenza il numero di cicli da compiere.
Nella for del C è ancora valida la logica a
tre coordinate, ma, a differenza della quasi totalità
dei linguaggi di programmazione, esse sono reciprocamente
svincolate e non necessarie. Ciò significa che, se in
Basic[5] la for agisce su un'unica variabile,
che viene inizializzata e incrementata (o decrementata) sino
al raggiungimento di un limite prestabilito, in C essa
può manipolare, ad esempio, tre diverse variabili (o
meglio, tre espressioni di diverso tipo); inoltre nessuna
delle tre espressioni deve necessariamente essere
specificata: è perfettamente lecita una for
priva di condizioni di iterazione.
A questo punto, tanto vale esaurire le banalità
formali, per concentrarsi poi sulle possibili modalità
di definizione delle tre condizioni che pilotano il ciclo.
Sia subito detto, dunque, che anche la for vuole che
le condizioni siano specificate tra parentesi tonde e che se
il blocco di codice del ciclo comprende più di una
istruzione sono necessarie le solite graffe, aperta e chiusa.
Anche nei cicli for possiamo utilizzare le
istruzioni break e continue: la prima per
"saltar fuori" dal ciclo; la seconda per tornare
"a bomba" alla valutazione del test. Anche i cicli
for possono essere annidati, e va tenuto presente
che il ciclo più interno compie una serie completa di
iterazioni ad ogni iterazione di quello che immediatamente lo
contiene.
E vediamo, finalmente, qualche ciclo for dal vivo:
nella sua forma banale, quasi "Basicistica" ,
può assumere il seguente aspetto:
for(i = 1; i < k; i++) {
....
}
Nulla di particolare. Prima di effettuare la prima
iterazione, la variabile i è inizializzata a
1. Se essa risulta minore della variabile k
il ciclo è eseguito una prima volta. Al termine di
ogni iterazione essa è incrementata e successivamente
confrontata con la k; se risulta minore di
quest'ultima il ciclo è ripetuto.
Vale la pena di evidenziare che le tre coordinate logiche
stanno tutte quante all'interno delle parentesi tonde e
sono separate tra loro dal punto e virgola
(";"); solo la sequenza (;;) deve
obbligatoriamente essere presente in un ciclo
for.
In effetti possiamo avere una for come la
seguente:
for( ; ; ) {
....
}
Qual è il suo significato? Nulla è
inizializzato. Non viene effettuato alcun test. Non viene
modificato nulla. Il segreto consiste nel fatto che
l'assenza di test equivale a condizione sempre
verificata: la for dell'esempio definisce quindi
un'iterazione infinita. Il programma rimane intrappolato
nel ciclo finché si verifica una condizione che gli
consenta di abbandonarlo in altro modo, ad esempio con
l'aiuto di una break.
Ma si può fare di meglio...
for(i = 0; string[i]; )
++i;
Il ciclo dell'esempio calcola la lunghezza della stringa (terminatore nullo escluso). Infatti i è inizializzata a 0 e viene valutato se il carattere ad offset 0 in string è nullo; se non lo è viene eseguita l'unica istruzione del ciclo, che consiste nell'incrementare i. A questo punto è valutato se è nullo il byte ad offset 1 in string, e così iterando finché string[i] non è proprio il NULL finale. L'esempio appena presentato è del tutto equivalente a
for(i = 0; string[i]; i++);
Il punto e virgola che segue la parentesi tonda indica che non vi sono istruzioni nel ciclo. Le sole cose da fare sono, perciò, la valutazione della condizione e l'incremento di i finché, come nel caso precedente, string[i] non punta al NULL che chiude la stringa. Se poi volessimo includere nel calcolo anche il NULL, ecco come fare:
for(i = 0; string[i++]; );
Sissignori, tutto qui. Anche questo ciclo non contiene alcuna istruzione; tuttavia, in questo caso, l'incremento di i fa parte della condizione e (trattandosi di un postincremento) viene effettuato dopo la valutazione, quindi anche (per l'ultima volta) quando string[i] punta al NULL. E che dire della prossima?
for( ; *string++; ) {
....
}
Nulla di particolare, in fondo: viene verificato se *string è un byte non nullo e string è incrementato. Se la verifica dà esito positivo viene eseguito il codice del ciclo. Viene poi nuovamente effettuata la verifica, seguita a ruota dall'incremento, e così via. Quanti si sono accorti che questo ciclo for è assolutamente equivalente a un ciclo while? Eccolo:
while(*string++) {
....
}
In effetti si potrebbe dire che l'istruzione
while, in C, è assolutamente inutile, in
quanto può essere sempre sostituita dalla
for, la quale, anzi, consente generalmente di
ottenere una codifica più compatta ed efficiente
dell'algoritmo. La maggiore compattezza deriva dalla
possibilità di utilizzare contestualmente alla
condizione, se necessario, anche un'istruzione di
inizializzazione ed una di variazione. La maggiore efficienza
invece dipende dal comportamento tecnico del compilatore, il
quale, se possibile, gestisce automaticamente i contatori dei
cicli for come variabili register.
Gli esempi potrebbero continuare all'infinito, ma quelli
presentati dovrebbero essere sufficienti per evidenziare,
almeno a grandi linee, le caratteristiche salienti dei cicli
definiti mediante l'istruzione for. E' forse
il caso di sottolineare ancora una volta che il contenuto
delle parentesi tonde dipende fortemente dal ciclo che si
vuole eseguire e dall'assetto elaborativo che gli si
vuole dare, ma l'uso dei due punto e virgola è
obbligatorio. Il primo e l'ultimo parametro non devono
essere necessariamente inizializzare ed incrementare (o
decrementare) il contatore (o il medesimo contatore),
così come il parametro intermedio non deve per forza
essere una condizione da valutare. Ciascuno di questi
parametri può essere una qualunque istruzione C o
può venire omesso. Il compilatore, però,
interpreta sempre il parametro di mezzo come una condizione
da verificare, indipendentemente da ciò che è
in realtà: detto parametro è quindi sempre
valutato come vero o falso[6], e da esso dipendono
l'ingresso nel ciclo e le successive iterazioni.