La direttiva #include La definizione di una funzione-utente comporta alcuni passaggi: scelta e indicazione del prototipo della funzione implementazione della funzione Le funzioni della libreria standard sono state sempre utilizzate senza mai effettuare alcuna delle due operazioni descritte sopra. Dove si trovano i prototipi delle funzioni appartenenti alla libreria standard? I prototipi sono suddivisi in categorie (a seconda delle funzionalità rese disponibili dalle funzioni stesse) e a ciascuna categoria corrisponde un file, indicato con il termine header file, la cui estensione è.h stdio.h contiene i prototipi delle funzioni per la gestione dell'input/output stdlib.h contiene i prototipi delle funzioni di utilità generale math.h contiene i prototipi delle funzioni matematiche (potenza, esponenziale, logaritmo...) L'inclusione dei prototipi delle funzioni nel codice avviene grazie alla direttiva #include #include viene indicato con il termine direttiva in quanto non rappresenta un'istruzione C; infatti esso non rappresenta un'istruzione del linguaggio, e quindi non verrà letto dal compilatore vero e proprio, ma è piuttosto un comando diretto al preprocessore.
Il processo che dal file di testo scritto in linguaggio C porta alla creazione del file eseguibile è suddiviso in vari passaggi; il primo di questi viene svolto dal preprocessore, il cui compito è quello di: rimuovere i commenti dal codice interpretare le direttive per il preprocessore (#include non è l'unica direttiva utilizzabile; alcune altre direttive verranno presentate nel seguito del corso) Svolti questi compiti, il preprocessore produce un nuovo file di testo il quale viene poi letto dal compilatore per il controllo della correttezza sintattica del codice. Questo significa che il preprocessore NON controlla la correttezza del codice. Che cosa faccia il preprocessore per adempiere al compito descritto al punto (1) è abbastanza intuitivo, ovvero esso rimuove tutto il testo contenuto all'interno dei commenti (ossia il testo che sta dopo i simboli '//' oppure il testo compreso tra i simboli '/*' e '*/'). La direttiva #include La direttiva include prevede un parametro e tale parametro può essere compreso tra i simboli '<' e '>' oppure tra doppi apici; il parametro rappresenta il nome di un file. Quando il preprocessore incontra questa direttiva si comporta in questo modo: cerca il file specificato (che non deve, per forza, avere estensione.h) NE COPIA IL CONTENUTO (senza interpretarlo come codice C ma limitandosi ad eseguire le direttive eventualmente presenti al suo interno) nel file di testo contenente il codice I criteri con cui il preprocessore cerca il file sono i seguenti: a. se il nome del file è compreso tra i simboli '<' e '>' la ricerca avviene all'interno della directory in cui si trovano tutti gli header file della libreria standard (nei sistemi operativi tipo Linux o Unix la directory è /usr/include) ad es.: #include <nome_file>
b. indica al preprocessore di cercare il file nome_file all'interno della directory /usr/include se il nome del file è compreso tra doppi apici la ricerca avviene all'interno della directory in cui sta avvenendo la compilazione, ovvero la directory in cui si trova il file di testo contenente il codice C ad es.: #include "nome_file" indica al preprocessore di cercare il file nome_file all'interno della directory in cui si trova il codice che stiamo compilando In ENTRAMBI I CASI, se il file non si trova nella directory in cui viene effettuata la ricerca, il processo di compilazione si ferma e viene visualizzato un messaggio d'errore: No such file or directory Un modo per visualizzare l'azione del preprocessore è il seguente: 3. 4. aprite un file contenente uno dei codici che avete preparato fino ad ora; supponiamo che il nome del file sia miocodice.c aggiungete al file dei commendi ed inserite anche la direttiva all'interno di un commento, ovvero scrivete // salvate il file attivate il compilatore con il seguente comando 5. 6. 7. gcc -E miocodice.c e guardate cosa succede (l'opzione -E serve a far analizzare il codice al preprocessore e a visualizzare il risultato della sua azione, senza attivare l'analisi sintattica del codice) riaprite il file miocodice.c ed eliminate il commento davanti alla direttiva #include salvate il file attivate di nuovo il compilatore con il seguente comando gcc -E miocodice.c e confrontate il risultato con quanto visto sopra
Tra le altre cose, l'uso della direttiva #include permette di raggruppare i prototipi di tutte le funzioni di utilità all'interno di uno o più file, rendendo quindi il codice più leggibile e soprattutto garantendo che diversi codici, distinti tra loro, possano richiamare tali funzioni (come succede nel nostro caso ogni volta che richiamiamo la funzione printf()). Applichiamo questo metodo anche al codice prodotto ieri seguendo questi passaggi: 3. determiniamo quale sia la parte di codice che serve per la dichiarazione/implementazione delle funzioni e quale quella che corrisponde all'implementazione della funzione main() separiamo la prima parte dalla seconda ed inseriamo la prima in un nuovo file rendiamo accessibile i prototipi delle nostre funzioni al file contenente la funzione main() utilizzando la direttiva per il preprocessore #include Riprendiamo il codice di ieri. Supponiamo che esso sia stato salvato nel file analisi_char.c // inizio del codice per l'analisi dei caratteri char analisi_carattere(char); int main() char lettura; printf("\n Inserisci almeno 3 caratteri (e poi premi ENTER o INVIO): "); lettura = getchar(); // prelevo il primo carattere putchar('\n'); putchar('\n'); printf("\n ecco il primo carattere del buffer : %c",lettura); analisi_carattere(lettura);
lettura = getchar(); printf("\n ecco il secondo carattere del buffer : %c (codice ASCII %d)", lettura,lettura); analisi_carattere(lettura); lettura = getchar(); printf("\n ecco il terzo carattere del buffer : %c (codice ASCII %d)",lettura,lettura); analisi_carattere(lettura); lettura = getchar(); printf("\n ecco il quarto carattere del buffer : %c (codice ASCII %d)",lettura,lettura); analisi_carattere(lettura); return 0; char analisi_carattere(char car) int minusc,maiusc,numero,controllo,operatore_m; if(! ((car>= 0) && (car<= 127)) ) printf("\n il carattere %c appartiene alla tabella estesa",car); else minusc = (car >= 97) && (car<= 122); maiusc = (car>= 65) && (car<= 90); numero = (car>= 48) && (car<= 57); controllo = (car>= 0) && (car<= 31); appartiene = minusc maiusc numero controllo; if(appartiene) if(minusc) printf("\n il carattere %c e\' una lettera minuscola",car); if(maiusc) printf("\n il carattere %c e\' una lettera maiuscola",car); if(numero) printf("\n il carattere %c e\' un numero",car); else printf("\n il carattere %d e\' un carattere di controllo",car); else operatore_m = (car==42) (car==43) (car==45) (car==47); if( operatore_m ) printf("\n il carattere %c e\' un operatore matematico",car); else printf("\n il carattere %c e\' un segno di punteggiatura",car); return car; /* fine codice */
Per chiarezza, eliminiamo per un attimo i corpi delle funzioni (main() e analisi_carattere()). Facendo questo si ottiene: // inizio del codice analisi_char.c char analisi_carattere(char); indicazioni di carattere generale (possono essere utili anche per main()) prototipo della funzione int main() // qui ci va il corpo di main() codice relativo a main(); eventuali chiamate alla funzione char analisi_carattere(char car) // qui ci va il corpo della funzione analisi_carattere() implementazione della funzione /* fine codice */ Risulta chiaro che le parti evidenziate contengono codice relativo alla sola funzione e possono perciò essere rimosse dal file analisi_char.c Apriamo un secondo file chiamato, per esempio, mie_funzioni.h, ed inseriamo all'interno di questo file tutto cio che è necessario per la corretta definizione della funzione. Il resto del codice possiamo lasciarlo all'interno del file analisi_char.c. mie_funzioni.h analisi_char.c
prototipo della funzione implementazione della funzione corpo di main() char analisi_carattere(char); char analisi_carattere(char car) /* implementazione della funzione analisi_carattere() */ #include "mie_funzioni.h" int main() /* implementazione della funzione main() */ /* c'è qualcosa di superfluo qui sopra? */ La proposta per l'esercizio di oggi è la seguente: a) Modificare il programma per l'analisi dei caratteri in modo tale da scomporlo in due file: uno contenente il prototipo e l'implementazione della funzione per l'analisi dei caratteri l'altro contenente il codice con le chiamate alla funzione per l'analisi dei caratteri b) Fare in modo che i caratteri da analizzare non vengano "forniti" al codice mediante chiamate a getchar() ma vengano generati in modo casuale.
Per generare in modo casuale dei caratteri possiamo servirci di una funzione della libreria standard che funge da generatore casuale di numeri. Tale funzione è inserita nel file stdlib.h int rand(void) La funzione non accetta parametri e restituisce un valore di tipo intero; ogni volta che la funzione viene richiamata essa produce un numero casuale compreso tra 0 ed un valore massimo pari a circa 65000. Come facciamo ad ottenere, a partire da un numero compreso tra 0 e 65000, un numero che possa essere interpretato come il codice ASCII di un carattere (ovvero compreso tra 0 e 255)? Una possibilità è la seguente: utilizzare l'operatore % #include <stdlib.h> #include "mie_funzioni.h" int main() int carattere, casuale; casuale = rand(); // assegno a casuale un valore a caso compreso tra 0 e 65000 carattere = casuale % 256 ; // ora carattere assume un valore compreso tra 0 e 255 printf("ecco il carattere generato casualmente: %c",carattere); analisi_carattere(carattere); return 0;