Una struttura di controllo, in informatica e specificatamente nei paradigmi di programmazione imperativa, è un costrutto sintattico di un linguaggio di programmazione la cui semantica afferisce al controllo del flusso di esecuzione di un programma, ovvero servono a specificare se, quando, in quale ordine e quante volte devono essere eseguite le istruzioni che compongono il codice sorgente in base alle specifiche di progetto del software da realizzare.

Strutture fondamentali Sequenza La sequenza è la struttura di controllo fondamentale di qualsiasi linguaggio imperativo, inclusi i linguaggi macchina. Stabilisce l'ordine in cui le istruzioni presenti nel testo del programma devono essere eseguite a tempo di esecuzione. Di norma, non ha una espressione sintattica esplicita: invece è data dalla semplice successione delle istruzioni; in tal caso, la macchina astratta del linguaggio in uso le esegue in sequenza, una dopo l'altra.

Go to

Insieme alla sequenza, il go to (vai a) è la struttura di controllo più semplice; anch'essa appare, in qualche forma, in tutti i linguaggi macchina. Il significato generale del goto è quello di "salto" ovvero far "passare" il controllo a una istruzione specificata, che può trovarsi in un punto qualsiasi del programma. Il goto ammette sempre anche (o solo) una forma condizionata, il cui significato può essere parafrasato come segue: "se è vera una condizione C, vai alla istruzione I". Nei linguaggi macchina, la condizione C deve solitamente assumere una delle due seguenti forme: "il contenuto della cella di memoria M è 0" oppure "il contenuto della cella di memoria M è diverso da 0"; l'istruzione I viene inoltre identificata dall'indirizzo di memoria in cui I è memorizzata. Nei Linguaggi ad alto livello come il C, la condizione C può essere espressa come una qualsiasi espressione booleana (come il risultato della moltiplicazione di A per B è diverso da X), e l'istruzione I può essere identificata da un nome o da un codice numerico esplicitamente associato dal programmatore all'istruzione stessa (indipendentemente dalla sua collocazione in memoria).

A partire dagli anni settanta, la struttura di controllo goto è stata soggetta a forti critiche in quanto essa consente (o favorisce) lo sviluppo di programmi potenzialmente molto poco leggibili e modificabili (il cosiddetto spaghetti code). Sebbene essa resti una struttura di controllo fondamentale dei linguaggi macchina moderni, nei linguaggi di programmazione ad alto livello, a seconda dei casi, il goto non viene fornito oppure viene fornito ma se ne sconsiglia l'uso.

Strutture di controllo della programmazione strutturata Nel 1966, con un celebre teorema, Corrado Böhm e Giuseppe Jacopini introdussero i fondamenti teorici del paradigma della programmazione strutturata dimostrando che qualsiasi programma scritto usando la struttura di controllo goto poteva essere riscritto usando solo strutture di controllo di tipo sequenza, iterazione e alternativa. Unitamente alle critiche di cui si è detto sopra, questo risultato contribuì a decretare la fine della programmazione basata sul goto. Tutti i linguaggi moderni offrono un insieme di strutture di controllo dei tre tipi introdotti da Böhm e Jacopini, sebbene molti mantengano anche il goto (pur sconsigliandone l'uso indiscriminato).

Alternativa Le strutture di controllo "alternative" consentono di specificare che una data istruzione o un dato blocco di istruzioni devono essere eseguiti "(solo) se" vale una certa condizione. Sono perciò anche dette strutture condizionali.

Alternativa if-then e if-then-else

L'alternativa if-then (se-allora) è la più semplice forma di alternativa. Il suo significato può essere parafrasato con la frase "se vale la condizione C, esegui l'istruzione (blocco) I". La maggior parte dei linguaggi di programmazione ammette anche (come variante) la forma più articolata if-then-else (se-allora-altrimenti), che si può parafrasare come: "se vale la condizione C esegui l'istruzione (blocco) I1; altrimenti esegui l'istruzione (blocco) I2".

Alternativa switch-case

L'alternativa switch-case (casi alternativa) può essere assimilata a una catena di if-then-else con certe restrizioni. In questo caso, la scelta di uno fra N istruzioni o blocchi alternativi viene fatta sulla base del valore di una determinata variabile o espressione, normalmente di tipo intero. Essa può essere parafrasata come segue: "valuta il valore N; nel caso in cui il suo valore sia V1, esegui I1; nel caso in cui sia V2, esegui I2 (ecc.)". Il seguente frammento di codice pseudo-C illustra il concetto:

case sceltaMenu of 1: apriFile(); 2: chiudiFile(); 3: salvaFile(); 4: esci(); end;

Il fatto che il valore N debba (spesso) essere di tipo intero è legato a considerazioni di efficienza nell'implementazione del meccanismo. Il case si presta infatti a essere trasformato, a livello di linguaggio macchina, in un goto con indirizzamento indiretto, che potrebbe essere basato su una tabella in memoria di cui N seleziona una particolare entry, in cui è specificato l'indirizzo delle istruzioni da eseguire per quel valore di N.

Iterazione

Le strutture di controllo "iterative" consentono di specificare che una data istruzione o un dato blocco di istruzioni devono essere eseguiti ripetutamente. Esse vengono anche dette cicli. Ogni struttura di controllo di questo tipo deve consentire di specificare sotto quali condizioni l'iterazione (ripetizione) di tali istruzioni debba terminare, ovvero la condizione di terminazione del ciclo oppure, equivalentemente, la condizione di permanenza nel ciclo. Di seguito si esaminano le strutture di controllo più note di questa categoria.

Ciclo for

Il ciclo for è indicato quando il modo più naturale per esprimere la condizione di permanenza in un ciclo consiste nello specificare quante volte debbano essere ripetuti l'istruzione o il blocco controllati dal ciclo. Le forme tradizionali di ciclo for possono essere parafrasate come "ripeti (il codice controllato) per i che va da un certo valore iniziale a un certo valore finale, con un certo passo". i è in generale una variabile di tipo intero, detta contatore. Il seguente frammento di codice BASIC illustra il concetto:

FOR I=1 TO 9 STEP 2 PRINT I NEXT I

Questo frammento ripete l'istruzione di stampa a video della variabile contatore I. Essa parte dal valore iniziale 1 per arrivare al valore finale 10. L'indicazione del "passo" (STEP) specifica come varia il valore di I da una iterazione alla successiva. Il frammento dunque stamperà la sequenza 1, 3, 5, 7, 9.

Ciclo while Il ciclo while (mentre, o fintantoché) è indicato quando la condizione di permanenza in un ciclo è una generica condizione booleana, indipendente dal numero di iterazioni eseguite. Le forme tradizionali di ciclo while possono essere parafrasate come "ripeti (il codice controllato) fintantoché resta vera la condizione C". Un esempio tipico è la lettura di dati da un di cui non si conosca a priori la dimensione; esso potrebbe assumere la forma "leggi il prossimo dato finché non incontri la fine del file". Il seguente frammento di codice pseudo-C mostra un esempio di while:

passwordUtente = leggiPassword(); while(passwordUtente <> passwordCorretta) { segnalaErrorePassword(); passwordUtente = leggiPassword();

Nel while del C (e di molti altri linguaggi) la condizione di permanenza nel ciclo viene controllata prima di eseguire la prima iterazione del ciclo stesso; se essa risulta immediatamente falsa, le istruzioni nel ciclo non vengono eseguite. Nell'esempio riportato sopra, se la password letta è corretta, il blocco di codice che segnala l'errore di immissione e chiede di inserire nuovamente la password non viene (ovviamente) eseguito.

Ciclo loop-until

Il ciclo loop-until (ripeti finché) differisce dal while per due aspetti. Innanzitutto, esso garantisce che venga eseguita sempre almeno una iterazione del ciclo (la condizione che controlla il ciclo viene verificata dopo aver concluso la prima iterazione). In secondo luogo, viene espressa la condizione di terminazione del ciclo anziché quella di permanenza nel ciclo (quest'ultima differenza non ha comunque un impatto concettuale molto importante, essendo le due condizioni esprimibili semplicemente come negazione l'una dell'altra). Il seguente frammento pseudo-Pascal illustra il concetto:

loop passwordUtente:= leggiPassword(); until (passwordUtente = passwordCorretta)

Varianti di while e loop-until

Le due differenze citate sopra fra while e loop-until sono in effetti indipendenti l'una dall'altra, per cui sono facilmente immaginabili (benché meno diffuse per motivi storici) anche altre due combinazioni: cicli che garantiscono una iterazione ma in cui si specifica la condizione di permanenza nel ciclo, e cicli che ammettono 0 iterazioni ma in cui si specifica la condizione di terminazione del ciclo. Un esempio del primo caso è la struttura do-while (fai fintantoché) del C e dei suoi derivati, esemplificata in questo frammento di pseudo-codice:

do passwordUtente = leggiPassword(); while(passwordUtente<>passwordCorretta);

Iterazione basata su collezioni

Alcuni linguaggi (per esempio Smalltalk, Perl, C#, Java) forniscono varianti del ciclo for in cui il "contatore" è una variabile generica (non necessariamente un numero intero) che assume una sequenza di valori del tipo corrispondente, per esempio tutti i valori contenuti in un array o in una collezione. Il seguente frammento di codice C# illustra il concetto:

foreach (string s in unaCollezioneDiStringhe) { stampa(s); }

Terminazione anticipata di cicli e iterazioni

Molti linguaggi forniscono una istruzione specifica per terminare "prematuramente" un ciclo, ovvero causarne la terminazione in un modo alternativo rispetto a quello principale del ciclo (per esempio basato sul valore del contatore nel for o sulla verità/falsità di una condizione nei cicli while o repeat-until). Il seguente frammento illustra il concetto in un ciclo Java:

// cerco un valore N in un array boolean trovato = false; for(int i=0; i<100; i++) if(v[i]==N) { trovato = true; break;

In questo frammento Java, il programma deve stabilire se un dato numero N è contenuto in un array. Non appena N viene trovato, diventa inutile proseguire l'attraversamento dell'array stesso: il break termina quindi il ciclo for.

L'uso indiscriminato di meccanismi come il break (fornito da linguaggi come C, C++ e Java) è spesso criticato e sconsigliato perché si viene a perdere una utile proprietà di leggibilità dei cicli della programmazione strutturata: infatti, di fronte a un ciclo while con una condizione C, il lettore tende ad assumere che, al termine del ciclo, C sia falsa. L'uso di break o costrutti simili, introducendo "un altro" possibile motivo di terminazione del ciclo (oltretutto potenzialmente "nascosto" all'interno del blocco controllato dal while) fa sì che questa assunzione non del tutto sicura. Tuttavia, vi sono anche casi in cui lo stato del programma alla fine del ciclo, con o senza break, è lo stesso, per cui il suddetto problema non si pone. Questo è il caso dell'esempio Java riportato sopra (il contatore i è locale al for, e quindi viene deallocato al termine del ciclo).

Un meccanismo simile (in generale considerato meno pericoloso) è quello che consente di terminare anticipatamente una determinata iterazione di un ciclo, passando immediatamente alla successiva. Si veda il seguente programma:

for(int i=1; i<=100; i++) if(numeroPrimo(i)) { stampa(i); continue; } // calcola e stampa i fattori di i }

Questo programma stampa i fattori di tutti i numeri interi da 1 a 100. Se i è un numero primo, è sufficiente stampare il numero stesso; altrimenti sono necessarie altre operazioni per scomporlo. L'istruzione continue indica che l'iterazione corrente è conclusa e fa sì che il ciclo passi immediatamente alla successiva.

Annidamento