Istruzione If in Linguaggio C

Il linguaggio di programmazione C mette a disposizione l'istruzione condizionale if che permette di eseguire del codice solo nel caso in cui una condizione risulta vera, ossia diversa da zero.

A seconda del valore della condizione di controllo l'istruzione if eseguirà o meno un'istruzione che può essere semplice o composta.

Esiste anche la possibilità, attraverso la clausola else, di specificare un'altra istruzione che venga eseguita nel caso opposto, ossia quando la condizione non è soddisfatta cioè pari a zero.

In questa lezione vedremo la sintassi delle istruzioni if e delle clausole else. Vedremo, anche, come gestire casi più complessi in cui è necessario concatenare oppure innestare più istruzioni if tra di loro.

Concetti Chiave
  • Un'istruzione if è composta da una condizione di controllo e da un'istruzione;
  • L'istruzione viene eseguita se e soltanto se la condizione di controllo risulta diversa da zero ossia vera;
  • L'istruzione può essere semplice o composta, ossia composta a sua volta da più istruzioni semplici;
  • Per specificare un'istruzione da eseguire quando la condizione di controllo risulta falsa, cioè pari a zero si utilizza la clausola else;
  • Le istruzioni if possono essere concatenate e innestate tra di loro per casi in cui le condizioni da verificare non si riducano semplicemente al caso vero e falso.

Istruzione if

L'istruzione if permette ad un programma di scegliere tra due alternative valutando il risultato di un'espressione. Nella sua forma più semplice, l'istruzione if ha il seguente aspetto:

if ( espressione ) istruzione;

La prima cosa da notare è che le parentesi attorno all'espressione sono obbligatorie. Inoltre, a differenza di altri linguaggi di programmazione, l'istruzione if non prevede la parola chiave then.

Quando un'istruzione if viene eseguita, l'espressione tra parentesi viene valutata. Se il risultato di tale espressione è diverso da zero, che come abbiamo visto nella lezione sulle espressioni logiche viene interpretato come falso dal linguaggio C, allora l'istruzione che segue l'espressione viene eseguita.

Utilizzando un diagramma di flusso possiamo rappresentare l'istruzione if in questo modo:

Diagramma di flusso dell'istruzione if
Figura 1: Diagramma di flusso dell'istruzione if

Nel diagramma, l'istruzione if è rappresentata come un rombo con all'interno la condizione da verificare. Nel caso in cui quest'ultima sia vera viene eseguita l'istruzione, altrimenti il controllo di flusso prosegue avanti. Per tal motivo l'istruzione if rappresenta un'istruzione condizionale.

Definizione

Istruzione if

Un'istruzione if è un'istruzione condizionale che permette di eseguire o meno un'istruzione a seconda del valore di una condizione.

La sintassi di un'istruzione if è la seguente:

if (espressione_logica)
    istruzione;

Dove espressione_logica è un'espressione logica, ossia un'espressione che viene considerata vera se il suo valore è diverso da zero. In tal caso l'istruzione istruzione viene eseguita.

Proviamo a chiarire con un esempio:

if (a > b)
    printf("A è maggiore di B\n");

In questo esempio l'istruzione printf viene eseguita se la condizione a > b risulta essere vera, ossia se ha un valore diverso da zero.

Nota

Attenzione a non confondere l'uguaglianza con l'assegnamento.

Bisogna prestare attenzione, quando si usa l'istruzione if, a non confondere l'operatore di uguaglianza == con l'operatore di assegnamento =.

Infatti, l'istruzione if che segue:

if (a == 0)

è ben diversa dalla seguente:

if (a = 0)

Infatti, nel secondo caso l'espressione tra parentesi, i = 0, assegna zero alla variabile i e restituisce il valore 0, per cui risulta sempre falsa.

Confondere l'uguaglianza, ==, con l'assegnamento, =, è forse uno degli errori più comuni che si trovano nei programmi scritti in C. La motivazione è data forse dal fatto che in matematica il simbolo di uguale = è identico al simbolo usato per l'assegnamento in linguaggio C.

Alcuni compilatori generano un warning se trovano un operatore di assegnamento dove normalmente dovrebbe esserci un operatore di uguaglianza, ma non bisogna fare affidamento su questa feature.

Consiglio

Verifica di appartenenza in un intervallo

Spesso, in linguaggio C si utilizza l'istruzione if per verificare se il valore di una certa variabile ricada o meno all'interno di un intervallo.

Ad esempio, potremmo voler verificare se il valore contenuto nella variabile i sia compreso tra 0 ed n. In termini matematici, vogliamo verificare se:

0 \leq i \leq n

Per far questo, in linguaggio C si può scrivere in questo modo:

if (0 <= i && i <= n)

Mentre, se vogliamo testare la condizione opposta possiamo scrivere in questo modo:

if (i < 0 || i > n)

Da notare che in questo secondo caso abbiamo usato l'operatore or, ||, anziché l'operatore and, &&.

Istruzioni composte

Prima, nel definire la sintassi dell'istruzione if abbiamo usato la forma seguente:

if ( espressione ) istruzione;

Da notare che istruzione è singolare, ossia l'istruzione if può essere seguita da una sola istruzione.

Come possiamo fare in modo che l'istruzione if determini l'esecuzione di due o più istruzioni? Possiamo utilizzare le istruzioni composte, ossia istruzioni della forma seguente:

{
    istruzione_1;
    istruzione_2;
    /* ... */
    istruzione_n;
}

In sostanza, mettendo le parentesi graffe attorno ad una sequenza di istruzioni forziamo il compilatore a trattarle come un'unica istruzione.

Definizione

Istruzioni composte

Un'istruzione composta è un'istruzione formata dalla sequenza di una o più istruzioni racchiuse tra parentesi graffe. La sintassi è la seguente:

{
    istruzione_1;
    istruzione_2;
    /* ... */
    istruzione_n;
}

Un esempio di istruzione composta è la seguente:

{
    printf("All'interno di un'istruzione composta\n");
    a++;
}

Da notare che le istruzioni interne all'istruzione composta terminano con un punto e virgola mentre l'istruzione composta non ha bisogno del punto e virgola finale.

A questo punto, possiamo combinare le istruzioni if e le istruzioni composte in questo modo:

if (a < b) {
    printf("All'interno di un'istruzione composta\n");
    a++;
}

L'istruzione composta da printf e dall'incremento della variabile a verrà eseguita se e soltanto se la condizione a < b risulta essere verificata (ossia diversa da zero).

Clausola else

Un'istruzione if può essere seguita da una clausola else in questo modo:

if ( espressione ) istruzione_1; else istruzione_2;

Con questa sintassi stiamo dicendo al compilatore di eseguire l'istruzione istruzione_1 se espressione ha un valore diverso da zero, oppure di eseguire l'istruzione istruzione_2 nel caso opposto.

Possiamo rappresentare la clausola else con un diagramma di flusso in questo modo:

Diagramma di flusso dell'istruzione if con clausola else
Figura 2: Diagramma di flusso dell'istruzione if con clausola else

Come è possibile osservare dal diagramma di flusso, l'istruzione if con clausola else permette di biforcare il controllo di flusso in due rami: il ramo vero che viene percorso nel caso in cui la condizione risulta vera, e il ramo falso che viene percorso in caso contrario.

Un possibile esempio di utilizzo di un'istruzione if con clausola else è il seguente:

if (a < b)
    max = b;
else
    max = a;

Bisogna notare che entrambe le istruzioni dei due rami sono terminate da un punto e virgola.

Definizione

Clausola else

La clausola else di un'istruzione if permette di selezionare un'istruzione nel caso la condizione risulti falsa. La sintassi da adoperare è la seguente:

if (condizione)
    istruzione_ramo_vero;
else
    istruzione_ramo_falso;
Consiglio

Piazzamento della clausola else

Da un punto di vista sintattico il compilatore C non impone nessun vincolo sul piazzamento della clausola else.

Possiamo, infatti, scrivere codice allineando la clausola else all'istruzione if in questo modo:

if (a < b)
    max = b;
else
    max = a;

Oppure, se le istruzioni sono brevi, possiamo essere più sintetici e scrivere il tutto così:

if (a < b) max = b;
else max = a;

Istruzioni if innestate

Non vi è alcuna restrizione sul tipo di istruzioni che possono apparire all'interno di un'istruzione if. Infatti, non è raro trovare istruzioni if innestate, ossia istruzioni if interne ad altre istruzioni if.

Prendiamo un esempio: vogliamo trovare il massimo tra tre variabili intere a, b e c. Possiamo implementare la ricerca del massimo in questo modo:

/* Trova il massimo tra a, b e c */
if (a > b)
    if (a > c)
        max = a;
    else
        max = c;
else
    if (b > c)
        max = b;
    else
        max = c;

Usando un diagramma di flusso, possiamo rappresentare lo stralcio di codice soprastante in questo modo:

Diagramma di flusso delle istruzioni if innestate dell'esempio
Figura 3: Diagramma di flusso delle istruzioni if innestate dell'esempio

Abbiamo realizzato la ricerca del massimo innestando più istruzioni if. Non vi è un limite al numero di istruzioni if che possono essere innestate. L'unico limite è, probabilmente, la leggibilità del codice. Infatti, all'aumentare delle istruzioni if innestate, il codice può diventare particolarmente complesso.

Definizione

Istruzioni if innestate

Le istruzioni if possono contenere al proprio interno altre istruzioni if; in tal caso si parla di istruzioni if innestate.

La sintassi generale per innestare istruzioni if è la seguente:

if (condizione_1)
    if (sub_condizione_1)
        istruzione_1;
    else
        istruzione_2;
else
    if (sub_condizione_2)
        istruzione_3;
    else
        istruzione_4;

Un modo per semplificare la scrittura di istruzioni if innestate è quello di adoperare le parentesi graffe anche quando queste non sono necessarie. Pertanto possiamo riscrivere il codice di sopra in questo modo:

/* Trova il massimo tra a, b e c */
if (a > b) {
    if (a > c)
        max = a;
    else
        max = c;
} else {
    if (b > c)
        max = b;
    else
        max = c;
}

Usare le parentesi graffe attorno alle istruzioni, anche quando non sono strettamente necessarie, è come usare le parentesi tonde nelle espressioni matematiche: aiutano a rendere il codice più leggibile e, allo stesso tempo, evitano che il compilatore possa interpretare il codice in maniera diversa rispetto all'intenzione con cui l'abbiamo scritto.

In alcuni casi, può essere utile aggiungere le parentesi graffe anche attorno alle istruzioni delle istruzioni if innestate, in questo modo:

/* Trova il massimo tra a, b e c */
if (a > b) {
    if (a > c) {
        max = a;
    } else {
        max = c;
    }
} else {
    if (b > c) {
        max = b;
    } else {
        max = c;
    }
}

Usare le parentesi graffe in questo modo ha due vantaggi:

  1. Il programma risulta più semplice da modificare in quanto possiamo aggiungere altre istruzioni alle clausole if o else in maniera rapida;
  2. Evita gli errori che possono scaturire dall'aver dimenticato le parentesi quando aggiungiamo istruzioni ad una clausola.

Istruzioni if concatenate

Possono verificarsi situazioni in cui la scelta che un programma debba effettuare non dipenda soltanto dal fatto che una condizione possa essere vera o falsa. Anzi, può capitare che il programma debba verificare una serie di condizioni e debba fermarsi non appena una di queste risulti vera.

In tal caso, in linguaggio C è possibile adoperare le istruzioni if concatenate (cascaded).

Per esempio, supponiamo di voler stampare un messaggio a schermo che ci dica se il valore di una variabile intera sia minore, uguale o maggiore di zero. In tal caso, non possiamo adoperare una semplice istruzione if ma dobbiamo metterne due in cascata, in questo modo:

if (a < 0)
    printf("a è minore di zero\n");
else
    if (a == 0)
        printf("a è uguale a zero\n");
    else
        printf("a è maggiore di zero\n");

Usando un diagramma di flusso, possiamo rappresentare il codice di sopra in questo modo:

Diagramma di flusso delle istruzioni if concatenate dell'esempio
Figura 4: Diagramma di flusso delle istruzioni if concatenate dell'esempio

Anche se la seconda istruzione if è innestata nella prima nella pratica non si usa l'indentazione per evidenziare la seconda istruzione if. Quello che si fa di solito è di far seguire gli if alle clausole else ed allineare gli else al primo if:

if (a < 0)
    printf("a è minore di zero\n");
else if (a == 0)
    printf("a è uguale a zero\n");
else
    printf("a è maggiore di zero\n");

Facendo così le istruzioni if concatenate assumono un aspetto tipico:

if ( espressione_1 )
    istruzione_1;
else if ( espressione_2 )
    istruzione_2;
else if ( espressione_3 )
    istruzione_3;
/* ... */
else if ( espressione_n )
    istruzione_n;
else
    istruzione_finale;

Questo modo di indentare le funzioni if concatenate evita il problema di avere codice eccessivamente indentato quando il numero di test da effettuare cresce a dismisura. Inoltre, ha il vantaggio di risaltare all'occhio come una semplice successione di test.

Definizione

Istruzioni if concatenate

Le istruzioni if concatenate sono istruzioni if che contengono all'interno della clausola else un'altra istruzione if e così via. Esse sono utili quando le condizioni da testare sono più di una ed esistono più casi differenti.

La forma generale delle istruzioni if innestate è la seguente:

if ( espressione_1 )
    istruzione_1;
else if ( espressione_2 )
    istruzione_2;
else if ( espressione_3 )
    istruzione_3;
/* ... */
else if ( espressione_n )
    istruzione_n;
else
    istruzione_finale;

Bisogna sempre ricordare che le istruzioni if concatenate non sono un nuovo tipo di istruzioni. Si tratta semplicemente di un'istruzione if ordinaria che ha al suo interno un'altra istruzione if nella sua clausola else, e così via.

Problema del Dangling else

Quando si adoperano le istruzioni if innestate bisogna fare attenzione al famigerato problema del "Dangling else" o, in italiano, else "appeso".

Per chiarire il tutto prendiamo l'esempio che segue:

if (b != 0)
    if (a != 0)
        result = a / b;
else
    printf("Errore: b è uguale a 0\n");

A prima vista lo stralcio di codice soprastante sembra essere corretto. Ad uno sguardo superficiale sembra che il codice verifichi dapprima che b sia diverso da zero e in caso contrario stampi un messaggio di errore evitando una divisione per zero. L'indentazione, però, ci trae in inganno.

In realtà il codice di sopra è ambiguo in quanto non è chiaro a quale delle due istruzioni if la clausola else appartenga. Il linguaggio C segue la convenzione di associare una clausola else all'istruzione if più prossima che non sia stata già accoppiata ad un'altra clausola else.

Per tal motivo, una versione correttamente indentata del codice precedente è la seguente:

/* Versione correttamente indentata */
if (b != 0)
    if (a != 0)
        result = a / b;
    else
        printf("Errore: b è uguale a 0\n");

In questo caso, quindi, la clausola else sarà sempre associata all'istruzione if più vicina. L'unico modo per cambiare questo comportamento è quello di utilizzare le parentesi graffe e forzare l'associazione all'istruzione if esterna, in questo modo:

/* Versione che associa l'else all'if esterno */
if (b != 0) {
    if (a != 0)
        result = a / b;
} else
    printf("Errore: b è uguale a 0\n");

Il problema del dangling else mostra l'importanza dell'utilizzo delle parentesi graffe. Usandole è possibile rendere il codice non ambiguo. Spesso, infatti, molti sviluppatori preferiscono usare sempre le parentesi anche quando non sono necessarie per non incappare in questo problema.

Definizione

Problema del Dangling else

In linguaggio C, in presenza di istruzioni if innestate, se non vengono esplicitamente racchiuse le istruzioni tra parentesi graffe, si usa la convenzione di associare un else al più vicino if di cui ne è sprovvisto.

In questo caso, la clausola else è associata al secondo if:

if (condizione_1)
    if (condizione_2)
        istruzione_1;
    else
        istruzione_2;

In quest'altro caso, usando esplicitamente le parentesi graffe, la clausola else è associata al primo if:

if (condizione_1) {
    if (condizione_2)
        istruzione_1;
} else
    istruzione_2;

In entrambe i casi, l'indentazione del codice è ininfluente.

In Sintesi

In questa lezione abbiamo visto la sintassi delle istruzioni condizionali if in linguaggio C. Abbiamo visto come specificare la condizione da verificare e come specificare le istruzioni da eseguire nel caso in cui la condizione sia vera, ossia diversa da zero, e viceversa attraverso la clausola else.

Per condizioni più complesse, che non si limitino al caso vero o falso abbiamo visto come concatenare più istruzioni if tra di loro. Inoltre, abbiamo visto anche come innestare più istruzioni if, l'una dentro l'altra, scoprendo, anche, che in tal caso vi sono delle insidie: il problema del Dangling else.

Nella prossima lezione studieremo la seconda istruzione condizionale messa a disposizione dal linguaggio C: l'istruzione switch