Funzioni



Prepariamo una funzione (da inserire nel file di intestazione personale) per l'analisi dei caratteri.
La funzione dovrà accettare come parametro una variabile di tipo char e stampare sul monitor tutte le informazioni che siamo in grado di estrarre dal codice ASCII della variabile stessa, ovvero:

  1. determinare se si tratti di una lettera e, nel caso, determinarne l'altezza (minuscola / maiuscola)
  2. determinare se si tratti di un carattere numerico
  3. determinare se si tratti di un segno di punteggiatura ( , . ; : ? " ' ! )
  4. determinare se si tratti di un simbolo matematico ( % + - * / < > = ( ) )
  5. determinare se si tratti di un carattere di controllo (^Z (= ctrl + Z), ^C, ^Y)
  6. determinare se si tratti di un simbolo non compreso nell'elenco precedente


Alcune di queste funzionalità sono state implementate in precedenza; a queste ne andranno aggiunte altre basate sul riconoscimento dei codici ASCII dei singoli caratteri.

Cominciamo con lo stabilire quale sarà il prototipo della funzione:
il parametro sarà una variabile di tipo char
il tipo del valore da restituire potrebbe essere un intero avente il seguente significato: se il confronto ha successo, ovvero il carattere viene identificato, la funzione restituisce un 2, altrimenti un 1.



// inizio file di intestazione myfun.h (versione 1)

/*
La funzione analisi_char() serve al riconoscimento dei caratteri. Se il parametro rientra tra i tipi di carattere previsti, la funzione restituisce un 2, altrimenti la funzione restituisce 1 (carattere non riconosciuto)
*/

#include <stdio.h>

int analisi_char(char);

int analisi_char(char carattere)
{int risultato;

 if( (carattere >= 65) && (carattere <= 90) )
   {printf("\nIl carattere inserito e\' una lettera maiuscola\n");
     risultato = 2;}

 else if ( (carattere >= 97) && (carattere <= 122) )
   {printf("\nIl carattere inserito e\' una lettera minuscola\n");
     risultato = 2;}

 else if( (carattere >= 48) && (carattere <= 57) )
   {printf("\nIl carattere inserito e\' un carattere numerico\n");
     risultato = 2;}

 else if( (carattere == 33) || (carattere == 34) || (carattere == 39) || (carattere == 44) ||
      (carattere == 46)|| (carattere == 58) || (carattere == 59) || (carattere == 63) || (carattere == 96))
   {printf("\nIl carattere inserito e\' un segno di punteggiatura\n");
     risultato = 2;}

 else if((carattere == 37) || (carattere == 40) || (carattere == 41) || (carattere == 43) ||
      (carattere == 45)|| (carattere == 47) || (carattere == 60) || (carattere == 61) || (carattere == 62))
   {printf("\nIl carattere inserito e\' un simbolo matematico\n");
     risultato = 2;}

 else if((carattere == 3) || (carattere == 25) || (carattere == 26))
   {printf("\nIl carattere inserito e\' un carattere di controllo\n");
     risultato = 2;}

 else
    risultato = 1;

 return risultato;
}

// fine file di intestazione myfun.h



// inizio file di intestazione myfun.h (versione 2, un po' più leggibile)

/*
La funzione analisi_char() serve al riconoscimento dei caratteri. Se il parametro rientra tra i tipi di carattere previsti, la funzione restituisce un 2, altrimenti la funzione restituisce 1 (carattere non riconosciuto)

La funzione char_punt() restituisce 1 se il carattere è un segno di punteggiatura, zero altrimenti

La funzione char_mat() restituisce 1 se il carattere è un segno di punteggiatura, zero altrimenti
*/

#include <stdio.h>

int analisi_char(char);
int char_punt(char);
int char_mat(char);


int analisi_char(char carattere)
{int risultato;

 if( (carattere >= 65) && (carattere <= 90) )
   {printf("\nIl carattere inserito e\' una lettera maiuscola\n");
     risultato = 2;}

 else if ( (carattere >= 97) && (carattere <= 122) )
   {printf("\nIl carattere inserito e\' una lettera minuscola\n");
     risultato = 2;}

 else if( (carattere >= 48) && (carattere <= 57) )
   {printf("\nIl carattere inserito e\' un carattere numerico\n");
     risultato = 2;}

 else if(char_punt(carattere))
   {printf("\nIl carattere inserito e\' un segno di punteggiatura\n");
     risultato = 2;}

 else if(char_mat(carattere))
   {printf("\nIl carattere inserito e\' un simbolo matematico\n");
     risultato = 2;}

 else if((carattere == 3) || (carattere == 25) || (carattere == 26))
   {printf("\nIl carattere inserito e\' un carattere di controllo\n");
     risultato = 2;}

 else
    risultato = 1;

 return risultato;
}



int char_punt(char carattere)
{int risultato = 0;
  if( (carattere == 33) || (carattere == 34) || (carattere == 39) || (carattere == 44) ||
      (carattere == 46)|| (carattere == 58) || (carattere == 59) || (carattere == 63) || (carattere == 96))
    risultato = 1;
 return risultato;}



int char_mat(char carattere)
{if((carattere == 37) || (carattere == 40) || (carattere == 41) || (carattere == 43) ||
      (carattere == 45)|| (carattere == 47) || (carattere == 60) || (carattere == 61) || (carattere == 62))
  return 1;
 else
  return 0;}


// fine file di intestazione myfun.h


NOTA: l'istruzione return fa sì che l'esecuzione della funzione in cui tale istruzione è inserita termini immediatamente e venga restituito il valore indicato.
Nel caso in cui la funzione debba restituire un valore e l'istruzione return venga inserita all'interno di istruzioni condizionali (if, if else, ...) è necessario che sia sempre garantito un valore di ritorno da parte della funzione stessa.

Ad esempio, la funzione che segue non è implementata correttamente in quanto nel caso in cui venga eseguito il pezzo di codice successivo all'else la funzione non restituisce alcun valore.


int char_mat(char carattere)
{if((carattere == 37) || (carattere == 40) || (carattere == 41) || (carattere == 43) ||
      (carattere == 45)|| (carattere == 47) || (carattere == 60) || (carattere == 61) || (carattere == 62))
  return 1;
 else
  printf("non trovo il carattere !");}




Esercizio

Cerchiamo ora di utilizzare la funzione appena implementata per analizzare l'input inserito dall'utente. Prepariamo un programma che effettui le seguenti operazioni:

  1. chieda all'utente di inserire un carattere
  2. analizzi il carattere inserito
  3. chieda all'utente di inserire un nuovo carattere fino a quando un determinato carattere non viene inserito


La funzione da utilizzare per accettare caratteri dall'utente è getchar() (-> la direttiva #include deve essere inclusa nel codice per inserire il file stdio.h) mentre il costrutto che ci consente di implementare le funzionalità indicate al punto 3 è il while().


while(condizione_di_continuazione)
    {corpo_del_while}


Implementazione: il ciclo che consente di inserire all'utente caratteri e di analizzarli può essere implementato usando due condizioni_di_continuazione diverse.


Caso 1: all'interno della condizione_di_continuazione avviene il controllo del carattere inserito dall'utente; se questo coincide con il carattere che, per convenzione, deve terminare il ciclo allora il ciclo deve terminare, altrimenti il ciclo deve proseguire

Caso 2: il ciclo while() viene strutturato come un ciclo infinito ed il controllo sul carattere viene fatto all'interno del ciclo stesso; appena il carattere convenuto viene trovato, il ciclo termina con un'istruzione break.




// inizio del codice test_char.c

#include "myfun.h" // basta questo, in quanto stdio.h è incluso al suo interno

int main()
{char car_inserito;
  int analisi,num_totale=0;

// inizio while (versione 1)
 while( (car_inserito = getchar()) != 1)    // corrisponde a CTRL + a
   {analisi = analisi_char(car_inserito);
     num_totale++;
     if(analisi==1)
      {printf("\n il carattere %c (codice ASCII %d) non è stato identificato",car_inserito,car_inserito);}
   }
// fine while

// visualizzo un messaggio che indichi quanti caratteri sono stati analizzati

 printf("\n Durante l'esecuzione del programma ");
 if(!num_totale) // quando num_totale vale 0
    printf("non sono stati analizzati caratteri\n");
 else if(num_totale==1)
    printf("e\' stato analizzato un carattere\n");
 else
    printf("sono stati analizzati %d caratteri\n",num_totale);
 
return 0;}


// fine del codice test_char.c




La parte compresa tra i due commenti "inizio while" e "fine while" poteva essere scritta anche in modo diverso.


(1) teniamo conto del buffer di input

// inizio while (versione 2)

 while( (car_inserito = getchar()) != 1)    // corrisponde a CTRL + a
   {if(car_inserito==10)
       {;} // salta automaticamente i caratteri "a capo" e non li conta !
     else
      {analisi = analisi_char(car_inserito);
       num_totale++;
       if(analisi==1)
        {printf("\n il carattere %c (codice ASCII %d) non è stato identificato",car_inserito,car_inserito);}
      }
   }

// fine while




















(2) Tra le parentesi del while possiamo inserire una qualsiasi espressione che restituisca un valore

// inizio while (versione 3)

while( ((car_inserito=getchar())!= 8) * (analisi = analisi_char(car_inserito))&&(++num_totale))
  {if(analisi==1)
      printf("\n il carattere %c (codice ASCII %d) non è stato identificato",car_inserito,car_inserito);
  }
// fine while

IMPORTANTE: nel preparare il while() sono state sfruttate le regole di priorità degli operatori (quelli aritmetici hanno priorità superiore a quelli relazionali) e quelle di associatività (gli operatori relazionali sono associativi da sinistra a destra).

(car_inserito=getchar())!= 8

Questa è la condizione che permette di valutare se il ciclo debba iniziare o meno. Se questa condizione è falsa, grazie alla modalità con cui opera il compilatore, NESSUNA delle successive espressioni viene valutata
 
analisi = analisi_char(car_inserito)

Se il carattere è diverso da quello che permette di terminare il ciclo, esso viene analizzato dalla funzione ed il valore restituito da quest'ultima viene assegnato alla variabile analisi. Per costruzione, analisi avrà sempre un valore il cui significato logico è VERO. Quindi il valore della variabile analisi non bloccherà mai il ciclo (come è giusto).
 
++num_totale

num_totale parte da 0. Visto che l'operatore ++ si trova prima della variabile, il suo valore viene prima incrementato e poi valutato, quindi anche quest'ultima espressione sarà sempre VERA e non bloccherà mai il ciclo; in particolare essa viene incrementata solo se la prima espressione ha valore VERO.
 



NOTA BENE: questa metodologia di implementazione non rende, in generale, il codice facilmente leggibile. L'esempio appena visto è stato proposto per mostrare quanto sia versatile il codice grazie alla modalità con cui il linguaggio C gestisce le variabili booleane (VERO / FALSO), non per mostrare un esempio di come preparare i cicli.








(3) Un'ultima versione prevede che il ciclo venga bloccato dall'interno del corpo del while, non a causa del valore della condizione_di_continuazione, che quindi deve risultare SEMPRE VERA (dobbiamo quindi creare un ciclo potenzialmente infinito)


// inizio while

 while(num_totale+1)    // inserisco (num_totale + 1) per evitare che non parta ma NON INCREMENTO
                                // il valore della variabile !
   {car_inserito = getchar();
     if(car_inserito==1)
       {break;}
     else if(car_inserito==10) // corrisponde al tasto ENTER
       {;}
     else
       {analisi = analisi_char(car_inserito);
         num_totale++;
         if(analisi==1)
          {printf("\n il carattere %c (codice ASCII %d) non è stato identificato",car_inserito,car_inserito);}
       } // fine if-else
   }

// fine while


?? si tratta veramente di un ciclo potenzialmente infinito ??










Se, per ipotesi, le variabili intere senza segno venissero rappresentate solo con 3 bit, allora potrei rappresentare numeri variabili da 0 fino a 7. Quindi, in questo mondo di variabili a 3 bit, troverei questi risultati:

001 + 001 = 010
100 + 011 = 111

111 + 001 = 000 !!! (non ho spazio per memorizzare il riporto)


















Facciamo un esempio basandoci su questi dati:

  1. utilzziamo una variabile di tipo char (perché è formata da soli 8 bit e quindi il ragionamento si semplifica)
  2. in base 2 si ha che: 1 + 1 = 0 (con il riporto di 1)

int main()
{unsigned char carattere=1; //

_00000001
 while(carattere)
   {





     carattere ++;




   }

_
00000001
_00000010
_00000011
...
...
_11111110
_11111111
100000000

... perdo il bit più significativo !
e torno a 0.

 return 0;}
 



Il ciclo quindi era solo apparentemente infinito, sarebbero state effettuate solo 255 iterazioni. Lo stesso risultato vale per le variabili di tipo int (in questo caso le iterazioni sarebbero state 4294967295).

Per ottenere un ciclo infinito devo usare, ad esempio, una sintassi di questo tipo:

while(1)
   {...}




Esercizio:

Sfruttando il fatto che i cicli aventi una condizione di continuazione simile a quella incontrata sopra non sono infiniti (ma vengono comunque eseguiti molte volte), costruiamo un ciclo che verifichi se rand() è una buona funzione per generare numeri casuali.
  1. Simuliamo un dado usando la funzione int_rand(1,6); se la funzione rand() lavora bene, dopo un numero sufficientemente elevato di lanci (ad esempio 4294967295) il numero di volte in cui esce 1 dovrebbe essere circa uguale al numero di volte in cui escono 2, 3, ...
  2. Calcoliamo il numero di volte in cui escono i 6 valori e, al termine dei ciclo, stampiamo questi 6 valori ed il rapporto tra il numero di volte in cui escono 2, 3, 4, 5 e 6 ed il numero di volte in cui esce il valore 1. Per svolgere questa operazione, ad ogni "lancio" del dado (ovvero, ogni volta che viene generato un numero casuale) dobbiamo incrementare il valore del contatore che corrisponde a tale valore.
  3. Se rand() opera correttamente questi ultimi cinque numeri dovrebbero essere prossimi a 1. La differenza tra questi ultimi e 1 è un parametro per valutare se la funzione rand() sia o meno un buon generatore di numeri casuali.


Traccia
:

  1. dichiarare 6 variabili di tipo intero ed inizializzarle a 0. Tali variabili servono a memorizzare il numero di volte in cui esce una data faccia del dado (ovvero il generatore di numeri casuali produce un determinato numero)
  2. Inizializzare il generatore di numeri casuali chiedendo all'untente di inserire dei caratteri.
  3. All'interno di un ciclo while(), avente come argomento un'espressione simile a quella utilizzata sopra per preparare il falso ciclo infinito, "lanciare i dadi" e, una volta ottenuto un determinato valore, incrementare il valore dell'intero corrispondente
  4. Al termine del ciclo visualizzare il numero di volte in cui ciascun numero è stato osservato
  5. Stampare il valore del rapporto tra i numeri di volte in cui compaiono i valori 2, 3, 4, 5, 6 ed il numero di volte in cui compare il valore 1