C Tutorial

Tutorial Programmazione C – Approfondimento Conditionals

Buongiorno a tutti,

in questo articolo approfondiamo il discorso introdotto nel quarto episodio del tutorial, parliamo cioè di Controllo di flusso e nello specifico delle istruzioni di selezione (Conditionals).

Fino ad ora ogni programma che abbiamo sviluppato procedeva in modo lineare, ogni riga di codice veniva eseguita nell’esatto ordine in cui erano scritte, senza possibilità di variare il percorso o di operare scelte. Come potete capire sono estremamente limitati i problemi che si possono risolvere con degli algoritmi lineari, un qualsiasi software che voglia avere un minimo di utilità deve poter operare delle scelte in base ai valori che le variabili assumono durante l’esecuzione. Provate a pensare, ad esempio, ad un programma che controlli una macchinina in grado di muoversi da sola. E’ necessario che il programma possa verificare se si trova vicino ad un ostacolo, in caso affermativo eseguire il codice necessario ad aggirarlo, in caso negativo far proseguire la macchina sull’attuale traiettoria.

Al fine di poter mettere in atto tali strategie, per fornirci quindi la possibilità di far agire il nostro software in base alle condizioni (allo stato in cui si trova in un determinato momento) il linguaggio di programmazione ci mette a disposizione degli strumenti: le istruzioni di selezione (conditionals in inglese).
Per poter “comunicare” al compilatore la nostra intenzione di utilizzare tali strutture dobbiamo utilizzare specifiche parole riservate dal linguaggio di programmazione allo specifico scopo di gestire queste istruzioni.

Al fine di poter comprendere i meccanismi che ci permetteranno di utilizzare queste strutture è necessario che sappiate usare gli operatori relazionali, vi invito quindi, se non l’avete già fatto, a leggere il precedente articolo prima di proseguire

In C sono presenti tre diverse strutture conditional:

-If-Else
-Switch
-L’operatore ternario

Analizziamo una per una per capirne il funzionamento.

If – Else
Si tratta della struttura più utilizzata e, per motivi che analizzeremo, l’unica considerata buona pratica di programmazione. Questa struttura ci fornisce un modo per dire al compilatore di eseguire un blocco di codice (vale a dire tutte le istruzioni raccolte all’interno di una coppia di parentesi graffe {}) o meno in base alla valutazione di un’espressione logica(cioè un espressione che porta ad un risultato di tipo vero/falso). Dunque ecco come si presenta quest’istruzione:


if(condizione)
{
//istruzioni da eseguire se la condizione risulta vera
}

E’ possibile anche fornire un blocco di codice alternativo da eseguire nel caso in cui la condizione risulti falsa, questo viene fatto attraverso la keyword else e si presenta in questo modo:


if(condizione)
{
//istruzioni da eseguire se la condizione risulta vera
} else
{
//istruzioni da eseguire se la condizione risulta falsa
}

E’ infine possibile concatenare una serie di istruzioni if-else aggiungendo un if dopo ogni else in questo modo:


if(condizione 1)
{
//istruzioni da eseguire se la condizione 1 risulta vera
} else if(condizione 2)
{
//istruzioni da eseguire se la condizione 2 risulta vera
} ... else if(condizione n)
{
//istruzioni da eseguire se la condizione n risulta vera
} ... else
{
//istruzioni da eseguire se la condizione n risulta falsa
}

switch-case
Il costrutto switch-case serve a valutare una variabile o un’espressione che restituisce un risultato di tipo intero e eseguire uno di una serie di blocchi di codice in base al numero. Un esempio chiarirà il tutto:


switch (variabile)
{
case 1: //codice da eseguire in caso la variabile abbia valore 1
case 2: //codice da eseguire in caso la variabile abbia valore 2
...
case n: //codice da eseguire in caso la variabile abbia valore n
default: //codice da eseguire in caso il valore non sia nessuno dei precedenti

Come vedete la struttura del costrutto sembra piuttosto semplice, ma in realtà una serie di problemi si annidano all’interno di questa struttura, primo fra tutti l’obbligo di mettere l’istruzione break alla fine di ogni case. L’istruzione break verrà analizzata nel dettaglio più avanti, per ora basti sapere che, quando incontrata, fa saltare il flusso del programma all’esterno del costrutto, nel nostro caso termina l’esecuzione dello switch.
Perché si rende necessario l’uso del break? perché una volta che incontra un case che corrisponde, vengono eseguiti tutti i blocchi di codice da li in poi. Torniamo un attimo al nostro esempio per chiarire meglio il concetto:
Il nostro switch ha dei case che vanno da 1 a n, supponiamo che il valore della nostra variabile sia 45, bene, a partire dal case 45 viene eseguito il codice di tutti i case fino al case n, senza più effettuare le verifiche. Questo può ovviamente causare errori di programmazione, errori che sono di difficile individuazione perché si tratta di errori run-time, errori quindi che si evidenziano solo quando il programma viene lanciato in quanto il compilatore non è in grado di rilevarli. Questo tipo di comportamento è in realtà voluto perché permette di associare più case ad un unico blocco di codice, in questo modo:


switch(variabile)
{
case 1:
case 2:
//istruzioni da eseguire in caso il valore sia 1 o 2
break;
case 3:
case 4:
case 5:
//altre istruzioni da eseguire in caso il valore sia 3, 4 o 5
break;
default: //istruzioni di default
}

Questo tipo di comportamento è definito FALL THROUGH e può causare seri bug, bug difficili da individuare, per questo motivo un numero sempre più ampio di programmatori considera l’utilizzo di questo costrutto una cattiva pratica di programmazione, anche perché qualsiasi comportamento ottenuto con il costrutto switch-case si può tranquillamente ottenere con l’uso di if-else concatenati. Un’ultimo esempio per mostrare che, talvolta, un uso volontario del FALL THROUGH può effettivamente essere il risultato voluto:


switch(unaVariabile)
{
case 1: printf("il codice qui presente viene eseguito con case 1\n"); break;
case 2: printf("Il codice qui presente viene eseguito con case 2\n"); //niente break; quindi avremo il fall through
case 3: printf("Il codice qui presente viene eseguito sia con case 3 che con case 2\n");break;

Operatore ternario
Per ultimo trattiamo dell’operatore ternario, che abbiamo già affrontato nell‘approfondimento sugli operatori in quanto è di fatto un operatore. Il funzionamento è molto simile a quello del costrutto if-else ed ha la seguente struttura:

(espressione logica) ? (//se vera) : (//se falsa);

Piuttosto criptica, ma appare più semplice in codice sorgente:


c = (a > b) ? a : b;

Quindi, alla variabile c verrà assegnato il risultato dell’operazione, vale a dire se a è maggiore di b, quindi se l’espressione è vera, a, in caso contrario b.

Per quanto allettante l’idea di poter scrivere un costrutto di tipo if-else in una sola riga l’utilizzo di questo costrutto tende a rendere il codice nebuloso e poco leggibile, è da molti considerata una cattiva pratica di programmazione e l’uso ne è sconsigliato.

Ti piaciono i miei tutorial e progetti? Aiutami a realizzarne altri. Supporta Zamein – Tutorials & Projects su Patreon!

One thought on “Tutorial Programmazione C – Approfondimento Conditionals

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *