[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 746.   Funzioni del linguaggio C

Il linguaggio C offre le funzioni come mezzo per realizzare la scomposizione del codice in subroutine. Prima di poter essere utilizzate attraverso una chiamata, le funzioni devono essere dichiarate, anche se non necessariamente descritte. In pratica, se si vuole indicare nel codice una chiamata a una funzione che viene descritta più avanti, occorre almeno dichiararne il prototipo.

Le funzioni del linguaggio C prevedono il passaggio di parametri solo per valore, con tutti i tipi di dati, esclusi gli array (che invece vanno passati per riferimento, attraverso il puntatore alla loro posizione iniziale in memoria).

Il linguaggio C, attraverso la libreria standard, offre un gran numero di funzioni comuni che vengono importate nel codice attraverso l'istruzione #include del precompilatore. In pratica, in questo modo si importa la parte di codice necessaria alla dichiarazione e descrizione di queste funzioni. Per esempio, come si è già visto, per poter utilizzare la funzione printf() si deve inserire la riga #include <stdio.h> nella parte iniziale del file sorgente.

746.1   Dichiarazione di un prototipo

Quando la descrizione di una funzione può essere fatta solo dopo l'apparizione di una sua chiamata, occorre dichiararne il prototipo all'inizio, secondo la sintassi seguente:

tipo nome ([tipo[ nome][,...]]);

Il tipo, posto all'inizio, rappresenta il tipo di valore che la funzione restituisce. Se la funzione non deve restituire alcunché, si utilizza il tipo void. Se la funzione utilizza dei parametri, il tipo di questi deve essere elencato tra le parentesi tonde. L'istruzione con cui si dichiara il prototipo termina regolarmente con un punto e virgola.

Lo standard C stabilisce che una funzione che non richiede parametri deve utilizzare l'identificatore void in modo esplicito, all'interno delle parentesi.

Segue la descrizione di alcuni esempi.

746.1.1   Esercizio

Scrivere i prototipi delle funzioni descritte nello schema successivo:

Nome della funzione Tipo di valore restituito Parametri
alfa
non restituisce alcunché x di tipo intero senza segno
y di tipo carattere
z di tipo a virgola mobile normale
beta
intero normale senza segno non ci sono parametri
gamma
numero a virgola mobile di tipo normale x di tipo intero con segno
y di tipo carattere senza segno

746.2   Descrizione di una funzione

La descrizione della funzione, rispetto alla dichiarazione del prototipo, richiede l'indicazione dei nomi da usare per identificare i parametri (mentre nel prototipo questi sono facoltativi) e naturalmente l'aggiunta delle istruzioni da eseguire. Le parentesi graffe che appaiono nello schema sintattico fanno parte delle istruzioni necessarie.

tipo nome ([tipo parametro[,...]])
{
    istruzione;
    ...
}

Per esempio, la funzione seguente esegue il prodotto tra i due parametri forniti e ne restituisce il risultato:

int prodotto (int x, int y)
{
    return (x * y);
}

I parametri indicati tra parentesi, rappresentano una dichiarazione di variabili locali(1) che contengono inizialmente i valori usati nella chiamata. Il valore restituito dalla funzione viene definito attraverso l'istruzione return, come si può osservare dall'esempio. Naturalmente, nelle funzioni di tipo void l'istruzione return va usata senza specificare il valore da restituire, oppure si può fare a meno del tutto di tale istruzione.

Nei manuali tradizionale del linguaggio C si descrivono le funzioni nel modo visto nell'esempio precedente; al contrario, nella guida GNU coding standards si richiede di mettere il nome della funzione in corrispondenza della colonna uno, così:

:-)

int
prodotto (int x, int y)
{
    return (x * y);
}

Le variabili dichiarate all'interno di una funzione, oltre a quelle dichiarate implicitamente come mezzo di trasporto degli argomenti della chiamata, sono visibili solo al suo interno, mentre quelle dichiarate al di fuori di tutte le funzioni, sono variabili globali, accessibili potenzialmente da ogni parte del programma.(2) Se una variabile locale ha un nome coincidente con quello di una variabile globale, allora, all'interno della funzione, quella variabile globale non è accessibile.

Le regole da seguire, almeno in linea di principio, per scrivere programmi chiari e facilmente modificabili, prevedono che si debba fare in modo di rendere le funzioni indipendenti dalle variabili globali, fornendo loro tutte le informazioni necessarie attraverso i parametri. In questo modo diventa del tutto indifferente il fatto che una variabile locale vada a mascherare una variabile globale; inoltre, ciò permette di non dover tenere a mente il ruolo di queste variabili globali e (se non si usano le variabili «statiche») fa sì che si ottenga una funzione completamente «rientrante».

746.2.1   Esercizio

Completare i programmi successivi con la dichiarazione dei prototipi e con la descrizione delle funzioni necessarie:

#include <stdio.h>
//
// Mettere qui il prototipo della funzione «fattoriale».
// 
// Mettere qui la descrizione della funzione «fattoriale».
//
int main (void)
{
    unsigned int x = 7;
    unsigned int f;
    f = fattoriale (x);
    printf ("Il fattoriale di %d è pari a %d.\n", x, f);
    getchar ();
    return 0;
}
#include <stdio.h>
//
// Mettere qui il prototipo della funzione «primo».
// 
// Mettere qui la descrizione della funzione «primo».
//
int main (void)
{
    unsigned int x = 11;
    if (primo (x))
      {
        printf ("%d è un numero primo.\n", x);
      }
    else
      {
        printf ("%d non è un numero primo.\n", x);
      }
    getchar ();
    return 0;
}
#include <stdio.h>
//
// Mettere qui il prototipo della funzione «interesse».
// 
// Mettere qui la descrizione della funzione «interesse».
//
// L'interesse si ottiene come capitale * tasso * tempo.
//
int main (void)
{
    double    capitale = 10000; // Euro
    double       tasso = 0.03;  // pari al 3 %
    unsigned int tempo = 3      // anni
    double   interessi;
    interessi = interesse (capitale, tasso, tempo);
    printf ("Un capitale di %f Euro ", capitale);
    printf ("investito al tasso del %f \% ", tasso * 100);
    printf ("Per %d anni, dà interessi per %f Euro.\n", anni, interessi);
    getchar ();
    return 0;
}

746.3   Vincoli nei nomi

Quando si definiscono variabili e funzioni nel proprio programma, occorre avere la prudenza di non utilizzare nomi che coincidano con quelli delle librerie che si vogliono usare e che non possano andare in conflitto con l'evoluzione del linguaggio. A questo proposito va osservata una regola molto semplice: non si possono usare nomi «esterni» che inizino con il trattino basso (_); in tutti gli altri casi, invece, non si possono usare i nomi che iniziano con un trattino basso e continuano con una lettera maiuscola o un altro trattino basso.

Il concetto di nome esterno viene descritto a proposito della compilazione di un programma che si sviluppa in più file-oggetto da collegare assieme (capitolo 575). L'altro vincolo serve a impedire, per esempio, la creazione di nomi come _Bool o __STDC_IEC_559__. Rimane quindi la possibilità di usare nomi che inizino con un trattino basso, purché continuino con un carattere minuscolo e siano visibili solo nell'ambito del file sorgente che si compone.

746.4   I/O elementare

L'input e l'output elementare che si usa nella prima fase di apprendimento del linguaggio C si ottiene attraverso l'uso di due funzioni fondamentali: printf() e scanf(). La prima si occupa di emettere una stringa dopo averla trasformata in base a dei codici di composizione determinati; la seconda si occupa di ricevere input (generalmente da tastiera) e di trasformarlo secondo codici di conversione simili alla prima. Infatti, il problema che si incontra inizialmente, quando si vogliono emettere informazioni attraverso lo standard output per visualizzarle sullo schermo, sta nella necessità di convertire in qualche modo tutti i dati che non siano già di tipo char. Dalla parte opposta, quando si inserisce un dato che non sia da intendere come un semplice carattere alfanumerico, serve una conversione adatta nel tipo di dati corretto.

Per utilizzare queste due funzioni, occorre includere il file di intestazione stdio.h, come è già stato visto più volte negli esempi.

Le due funzioni, printf() e scanf(), hanno in comune il fatto di disporre di una quantità variabile di parametri, dove solo il primo è stato precisato. Per questa ragione, la stringa che costituisce il primo argomento deve contenere tutte le informazioni necessarie a individuare quelli successivi; pertanto, si fa uso di specificatori di conversione che definiscono il tipo e l'ampiezza dei dati da trattare. A titolo di esempio, lo specificatore %d si riferisce a un valore intero di tipo int, mentre %ld si riferisce a un intero di tipo long int.

Vengono mostrati solo alcuni esempi, perché una descrizione più approfondita nell'uso delle funzioni printf() e scanf() appare in altri capitoli (585 e 605). Si comincia con l'uso di printf():

...
double capitale = 1000.00;
double tasso    = 0.5;
int    montante = (capitale * tasso) / 100;
...
printf ("%s: il capitale %f, ", "Ciao", capitale);
printf ("investito al tasso %f%% ", tasso);
printf ("ha prodotto un montante pari a %d.\n");
...

Gli specificatori di conversione usati in questo esempio si possono considerare quelli più comuni: %s incorpora una stringa; %f traduce in testo un valore che originariamente è di tipo double; %d traduce in testo un valore int; inoltre, %% viene trasformato semplicemente in un carattere percentuale nel testo finale. Alla fine, l'esempio produce l'emissione del testo: «Ciao: il capitale 1000.00, investito al tasso 0.500000% ha prodotto un montante pari a 1005.»

La funzione scanf() è un po' più difficile da comprendere: la stringa che definisce il procedimento di interpretazione e conversione deve confrontarsi con i dati provenienti dallo standard input. L'uso più semplice di questa funzione prevede l'individuazione di un solo dato:

...
int importo;
...
printf ("Inserisci l'importo: ");
scanf ("%d", &importo);
...

Il pezzo di codice mostrato emette la frase seguente e resta in attesa dell'inserimento di un valore numerico intero, seguito da [Invio]:

Inserisci l'importo: _

Questo valore viene inserito nella variabile importo. Si deve osservare il fatto che gli argomenti successivi alla stringa di conversione sono dei puntatori, per cui, avendo voluto inserire il dato nella variabile importo, questa è stata indicata preceduta dall'operatore & in modo da fornire alla funzione l'indirizzo corrispondente (si veda il capitolo 577 sulla gestione dei puntatori).

Con una stessa funzione scanf() è possibile inserire dati per diverse variabili, come si può osservare dall'esempio seguente, ma in questo caso, per ogni dato viene richiesta la separazione con spazi orizzontali o anche con la pressione di [Invio].

printf ("Inserisci il capitale e il tasso:");
scanf ("%d%f", &capitale, &tasso);

746.5   Restituzione di un valore

In un sistema Unix e in tutti i sistemi che si rifanno a quel modello, i programmi, di qualunque tipo siano, al termine della loro esecuzione, restituiscono un valore che può essere utilizzato da uno script di shell per determinare se il programma ha fatto ciò che si voleva o se è intervenuto qualche tipo di evento che lo ha impedito.

Convenzionalmente si tratta di un valore numerico, con un intervallo di valori abbastanza ristretto, in cui zero rappresenta una conclusione normale, ovvero priva di eventi indesiderati, mentre qualsiasi altro valore rappresenta un'anomalia. A questo proposito si consideri quello «strano» atteggiamento degli script di shell, per cui zero equivale a Vero.

Lo standard del linguaggio C prescrive che la funzione main() debba restituire un tipo intero, contenente un valore compatibile con l'intervallo accettato dal sistema operativo: tale valore intero è ciò che dovrebbe lasciare di sé il programma, al termine del proprio funzionamento.

Se il programma deve terminare, per qualunque ragione, in una funzione diversa da main(), non potendo usare l'istruzione return per questo scopo, si può richiamare la funzione exit():

exit (valore_restituito);

La funzione exit() provoca la conclusione del programma, dopo aver provveduto a scaricare i flussi di dati e a chiudere i file. Per questo motivo, non restituisce un valore all'interno del programma, al contrario, fa in modo che il programma restituisca il valore indicato come argomento.

Per poterla utilizzare occorre includere il file di intestazione stdlib.h che tra l'altro dichiara già due macro-variabili adatte a definire la conclusione corretta o errata del programma: EXIT_SUCCESS e EXIT_FAILURE.(3) L'esempio seguente mostra in che modo queste macro-variabili potrebbero essere usate:

#include <stdlib.h>
...
...
if (...)
  {
    exit (EXIT_SUCCESS);
  }
else
  {
    exit (EXIT_FAILURE);
  }

Naturalmente, se si può concludere il programma nella funzione main(), si può fare lo stesso con l'istruzione return:

#include <stdlib.h>
...
...
int main (...)
{
    ...
    if (...)
      {
        return (EXIT_SUCCESS);
      }
    else
      {
        return (EXIT_FAILURE);
      }
    ...
}

746.5.1   Esercizio

Modificare uno degli esercizi già fatti, dove si verifica se un numero è primo, allo scopo di far concludere il programma con EXIT_SUCCESS se il numero è primo effettivamente; in caso contrario il programma deve terminare con il valore corrispondente a EXIT_FAILURE.

In un sistema operativo in cui si possa utilizzare una shell POSIX, per verificare il valore restituito dal programma appena terminato è possibile usare il comando seguente:

echo $?[Invio]

Si ricorda che la conclusione con successo di un programma si traduce normalmente nel valore zero.

746.6   Riferimenti


1) Per la precisione, i parametri di una funzione corrispondono alla dichiarazione di variabili di tipo automatico.

2) Questa descrizione è molto semplificata rispetto al problema del campo di azione delle variabili in C; in particolare, quelle che qui vengono chiamate «variabili globali», non hanno necessariamente un campo di azione esteso a tutto il programma, ma in condizioni normali sono limitate al file in cui sono dichiarate. La questione viene approfondita in modo più adatto a questo linguaggio nel capitolo 575.

3) In pratica, EXIT_SUCCESS equivale a zero, mentre EXIT_FAILURE equivale a uno.


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome funzioni_del_linguaggio_c.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory