Livello 3: strutture condizionali

Le strutture condizionali consentono di estendere le funzionalità del vostro programma aggiungendo la possibilità di selezionare diverse azioni in base alla valutazione di specifiche condizioni. La più semplice istruzione che il C mette a disposizione per realizzare una struttura condizionale prende il nome di if. Tale istruzione valuta un’espressione (dove è possibile far uso di operatori relazioni e/o logici) e in base al risultato dell’espressione (vero o falso, true/false: 1 o 0) consente di eseguire un’istruzione (o un corpo, quindi più istruzioni). E’ anche possibile svolgere in alternativa un’altra istruzione (o un corpo, quindi più istruzioni) attraverso la specifica di un “ramo alternatico” individuato dall’istruzione else. Di seguito un semplice esempio:

//Programma che consente di valutare se un numero intero è pari o dispari
#include <stdio.h>
int main() {
  int n;

  printf("Type a positive integer: ");
  scanf("%d",&n);  
  if(n%2 == 0) //resto della divisione per 2
  	printf("\n%d is even\n",n);
  else
  	printf("\n%d is odd\n",n);
}

Occorre ricordare che in C qualsiasi valore diverso da zero viene interpretato come vero all’interno di un’espressione. L’uso di operatori relazioni e logici consente di incrementare il livello di complessità di un’espressione:

#include <stdio.h>
//Programma che consente di valutare se un anno è bisestile
int main() {
   int year;

   printf("Insert a year (i.e., 2018): ");
   scanf("%d",&year);
   if ((year%4 == 0 && year%100 != 0) || year%400 == 0)
      printf("\n%d is a leap year\n", year);
   else printf("\n%d is not a leap year\n", year);
}

E’ possibile avvalersi in C anche di un’altra istruzione: switch-case. Questa istruzione permette di selezionare uno specifico blocco di istruzioni sulla base di un valore (di tipo int o char) assunto da una variabile o da un’espressione confrontandolo poi con una lista di valori costanti (di tipo int o char). Vediamo un semplice esempio:

//Programma che consente di valutare se un numero intero è pari o dispari
#include <stdio.h>
int main() {
  int n;
  printf("Type a positive integer: ");
  scanf("%d",&n);
  switch (n % 2)
  {
    case 0:
    	printf("\nThe number %d is even\n",n);
    	break;
    case 1:
      printf("\nThe number %d is odd\n",n);
    	break;
  }
}

E’ possibile definire un blocco default per intercettare valori non presenti nella lista dei casi (ovviamente in questo specifico esempio non ha senso definire un caso default) precedentemente definiti. Infine, l’istruzione break evita che vengano eseguite anche le istruzioni del case successivo.

Livello 4: i cicli

Per poter sviluppare un codice più potente, più chiaro e conciso hai certamente necessità di utilizzare le istruzioni iterative. Quando hai necessità di eseguire un’istruzione più di una volta allora significa che hai necessità di scrivere un ciclo. L’istruzione iterativa più utilizzata è il for. La sua forma più comune è del tipo: for(i=0;i<n;i++) seguita da un blocco oppure da una singola istruzione. In sostanza in un ciclo for esistono tre campi separati da il ;. Il primo campo serve per inizializzare la variabile di controllo del ciclo (tiene memoria di quanti cicli vengono eseguiti) e tale inizializzazione (per esempio i = 1) viene eseguita solo una volta prima che il ciclo abbia effettivamente inizio. Tieni presente che questo passaggio è di grande importanza e un errore di inizializzazione può creare seri problemi al funzionamento del tuo codice (spesso non rilevabili in fase di compilazione). Il secondo campo contiene un’espressione (per esempio i <= n) che definisce la condizione che deve essere soddisfatta al fine che il ciclo vada avanti nell’esecuzione dell’istruzione (o delle istruzioni). Tale espressione viene valutata dopo l’inizializzazione e prima dell’esecuzione del ciclo. Se quindi, all’inizio la condizione risulta essere falsa l’istruzione (le istruzioni) definite nel ciclo non verrà mai eseguita. Il terzo campo contiene l’incremento (o decremento a seconda della necessità) della variabile di controllo del ciclo (i = i + 1 che può essere scritta anche come i++ o ++i), in modo tale che il valore di i possa variare ed evita quindi la realizzazione di un ciclo infinito. Vediamo un semplice esempio sull’uso dell’istruzione for:

//Programma che prende in input n numeri interi e ne calcola somma e media
#include <stdio.h>
int main() {
  int i,n,num,sum=0;
  float mean;

  printf("How many integers you want to add? ");
  scanf("%d",&n);
  for(i=1;i<=n;++i) {
    printf("\nInteger #%d: ",i);
    scanf("%d",&num);
    sum += num; //equivale a scrivere: sum = sum + num;
  }
  mean=(float)sum/n; //(float) indica un cast: promozione di sum a tipo float
  printf("\nThe mean is equal to: %f\n",mean);
}

E’ a mio avviso importante fare attenzione quando si usa l’operatore += in quanto è meno visibile il fatto che la variabile sum si trovi anche a sinistra dell’assegnazione e che quindi è necessario in questo caso inizializzarla. Il cast è invece utile per evitare di eseguire una divisione tra interi che fornisce appunto un intero e che quindi renderebbe il calcolo della media poco preciso.

In C esiste un’altra istruzione che consente di realizzare dei cicli: il ciclo while. Nella maggior parte dei casi è possibile utilizzare una delle due istruzioni (for o while) in modo interscambiabile. Viene a mio parere più naturale preferire il for quando è noto a priori il numero di iterazioni che si vogliono ottenere. Attenzione infine che nel ciclo while non è esplicitamente presente un campo per l’inizializzazione della variabile di controllo. Vediamo l’esempio precedente scritto con l’istruzione while:

//Programma che prende in input n numeri interi e ne calcola somma e media
#include <stdio.h>
int main() {
  int i=1,n,num,sum=0;
  float mean;

  printf("How many integers you want to add? ");
  scanf("%d",&n);
  while(i<=n) {
    printf("\nInteger #%d: ",i);
    scanf("%d",&num);
    sum+=num;
    ++i;
  }
  mean=(float)sum/n;
  printf("\nThe mean is equal to: %f\n",mean);
}

Anche se è meno frequente il suo utilizzo, esiste anche l’istruzione do-while che ha la caratteristica di eseguire un’istruzione (o un blocco di istruzioni) subito prima della verifica della condizione.

Livello 5: array

Gli array rappresentano uno strumento molto potente. Quando hai necessità di gestire un insieme di variabili omogenee - quindi tutte dello stesso tipo - non hai necessità di dichiararle separatamente ma puoi più semplicemente usare un array. Un array è costituito quindi una sequenza di variabili, tutte con lo stesso nome, che si trovano in blocchi di memoria adiacenti e che quindi sono “fisicamente” collegate tra di loro. Come per ogni altra variabile, anche un array deve essere dichiarato. Con int v[10]; dichiariamo un array di nome v che contiene 10 elementi di tipo intero (int) adiacenti in memoria. In sostanza, 10 blocchi di memoria da 4 byte (per un totale di 40 byte) vengono resi disponibili per l’array v. Per poter accedere agli elementi dell’array si usa sempre l’etichetta v con un meccanismo noto come subscription: ogni elemento dell’array si distingue dagli altri elementi grazie all’uso di un indice - v[0] è il primo elemento e v[9] è l’ultimo. Come si evince da questo esempio, il primo elemento di un array ha indice pari a 0 e l’ultimo elemento ha indice pari a n-1 (dove n rappresenta la dimensione dell’array). Qualsiasi tentativo di accesso ad un elemento al di fuori di questo intervallo (0, ..., n-1) rappresenta un grave errore anche se non è rilevato in fase di compilazione. Buona pratica è quella di usare una direttiva al preprocessore - #define N 10 - per dimensionare l’array in fase di dichiarazione. Vediamo un primo esempio:

// Programma che consente di inserire n voti e di calcolarne la media
#include <stdio.h>
#define NGRADE 10
int main() {
  int i,n,grade[NGRADE],sum=0;
  float mean;

  printf("How many grades you want to insert? ");
  scanf("%d",&n); //Attenzione qui!!!
  for(i=0;i<n;++i) {
    printf("\nInsert grade #%d: ",i+1);
    scanf("%d",&grade[i]);
    sum+=grade[i];
    }
  mean=(float)sum/n;
  printf("\nThe mean is eqaul to: %f\n",mean);
}

A differenza di quanto visto in precedenza, questa volta - grazie all’uso degli array - tutti i voti inseriti da tastiera verranno conservati in memoria. E’ importante considerare che è responsabilità del programmatore progettare un array che abbia una dimensione sufficiente per il programma che sta realizzando. Se in futuro si volesse decidere di usare un array più grande sarebbe sufficiente andare a modificare solo il valore presente nella define. In questo esempio occorre evidenziare che l’istruzione scanf("%d",&n); può consentire all’utente di procedere all’inserimento di un numero di voti superiore a quello ammesso. Diventa pertanto assolutamente indispensabile cercare di evitare una situazione del genere. Vediamo come:

#include <stdio.h>
#define N 10
int main() {
  int i,n,v[N],sum=0;

  do {
    printf("Dimension? ");
    printf("It must be in the range 1-10!");
    scanf("%d",&n);
  } while(n<1 || n>N); 
  //usiti dal do-while n avrà un valore compreso tra quelli ammessi
  for(i=0;i<n;++i) {
    printf("\nType the integer #%d: ",i+1);
    scanf("%d",&v[i]);
    sum+=v[i];
    }
  printf("\nThe sum is: %d\n",sum);
}

L’esempio che segue mostra invece come calcolare media, massimo e minimo dei valori inseriti nell’array grades:

#include <stdio.h>
#define NGRADES 10
int main() {
  int i,n,grades[NGRADES],sum=0,min,max;
  float mean;

  do  {
    printf("How many grades? ");
    scanf("%d",&n);
  } while(n<1 || n>NGRADES);

  for(i=0;i<n;++i)  {
    printf("\nType grade #%d: ",i+1);
    scanf("%d",&grades[i]);
    sum+=grades[i];
    }
  mean=(float)sum/n;
  printf("\nThe mean is: %.1f\n",mean);

  min=grades[0];
  max=grades[0];
  for(i=0;i<n;++i) {
    if(grades[i]<min) min=grades[i];
    if(grades[i]>max) max=grades[i];
  }
  printf("\nThe max grade is: %d\n",max);
  printf("\nThe min grade is: %d\n",min);
}