Introduzione alle funzioni in linguaggio C

Scrivere codice che sia facile da leggere e manutenere può essere un compito molto difficile.

Spesso si dice the "i programmi dovrebbero essere scritti per essere letti dalle persone, e solo casualmente per essere eseguiti dalle macchine". Ciò significa che nello scrivere programmi bisogna concentrarsi sul realizzare codice che sia facile da comprendere da altri sviluppatori.

Le funzioni in linguaggio C aiutano a decomporre i programmi in "pezzi" più piccoli. Ciò rende il codice più semplice da creare, manutenere e testare.

Una funzione è un'unità di programma auto-contenuta progettata per svolgere un determinato compito. Quando si scrive un programma di grandi dimensioni, la cosa più intelligente da fare è quella di scomporre il programma stesso in "pezzi" più piccoli: le funzioni.

Una delle idee chiave del C è quella che ogni programma sia segmentato, o meglio modularizzato, in funzioni più piccole e semplici. A partire da esse è poi possibile costruire programmi complessi.

Come dicevano gli antichi romani: "divide et impera", ossia "dividi e comanda". Suddividendo il programma in funzioni logicamente entro-contenute che svolgono un compito ben predefinito, è possibile risolvere problemi di natura più ampia. Inoltre se suddividiamo il nostro programma in "pezzi" più semplici sarà più facile testarlo e verificarne il corretto comportamento. Infine, se una o più funzioni svolgono dei compiti ben precisi, esse potranno essere riutilizzate in altri programmi senza aver la necessità di riscrivere lo stesso codice o, peggio ancora, reinventare la ruota.

Struttura di un programma e funzioni

Come abbiamo detto, un programma C è composto da una o più funzioni. La più importante di tutte è la funzione main.

La funzione main rappresenta il punto in cui l'esecuzione del programma parte nel momento in cui viene avviato. Per questo motivo la si indica, in gergo tecnico, come entry point, ossia punto di ingresso. All'interno della funzione main siamo in grado di utilizzare altre funzioni, come abbiamo visto nei programmi delle lezioni precedenti, come ad esempio la printf. In C, quindi, una funzione è in grado di utilizzare altre funzioni a sua volta.

Per ottenere la modularità il linguaggio C adopera le funzioni. Ogni programma viene realizzato, solitamente, attraverso la combinazione di funzioni della libreria standard del C e nuove funzioni ad-hoc create dagli sviluppatori.

Libreria standard del C

La libreria standard del C mette a disposizione una vasta quantità di funzioni pronte all'uso che si occupano di effettuare calcoli matematici, di manipolare le stringhe, di gestire l'input e l'output e molto altro. Queste funzioni sono specificate nello standard del linguaggio C oltre alla sintassi del linguaggio stesso. Il vantaggio che ne risulta è che in questo modo ogni compilatore C deve fornire una libreria di funzioni che aderisca allo standard per cui, anche se cambiamo sistema operativo o processore, utilizzando il compilatore C associato avremo sempre a disposizione un insieme di funzioni pronte all'uso senza dover preoccuparci delle differenze che intercorrono tra i vari sistemi.

I programmi che utilizzano la libreria standard saranno, pertanto, portabili. Ciò significa che un programma scritto utilizzando solo funzioni della libreria standard, potranno essere compilati senza problemi sia con un compilatore per Windows che con un compilatore per Linux e così via. Le funzioni printf e scanf sono, ad esempio, funzioni standard di libreria.

Funzioni definite dall'utente

Ovviamente, le funzioni fornite dalla libreria standard del C non riescono a coprire tutte le possibili esigenze dei programmi reali. Sorgerà sempre, nell'ambito di un progetto, la necessità di realizzare una funzionalità nuova. Per questo motivo il linguaggio C mette a disposizione degli sviluppatori i mezzi e i costrutti necessari per implementare nuove funzioni che prendono il nome di funzioni definite dall'utente.

Il vantaggio nell'implementare funzioni personalizzate sta nel fatto che le istruzioni contenute nella nuova funzione sono scritte una volta sola e sono entro-contenute nella funzione stessa, tuttavia la funzione può essere invocata in un punto qualsiasi del nostro programma.

Nelle prossime lezioni vedremo come creare nuove funzioni e come richiamarle.

Invocazione di funzioni

Prima di addentrarci nei dettagli di come realizzare e richiamare le funzioni in linguaggio C, è necessario tuttavia chiarire alcuni concetti sul meccanismo di utilizzo.

In gergo tecnico, quando si vuole "utilizzare" una funzione si usa il termine invocazione.

Invocare una funzione significa cedere il controllo ad essa, in maniera tale che possa svolgere i suoi compiti. Per invocare una funzione si usa il meccanismo di chiamata a funzione. Una chiamata a funzione consiste nello specificare:

  1. Il nome della funzione.
  2. I parametri: ossia le informazioni di cui ha bisogno la funzione affinché possa svolgere il suo compito.

Per comprendere meglio il meccanismo di invocazione proviamo ad esaminare l'esempio che segue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>
#include <math.h>

int main() {
    double x = 5.0;
    double y;

    y = exp(x);

    printf("exp(5) = %f\n", y);

    return 0;
}

In questo esempio abbiamo utilizzato due funzioni: la printf che conosciamo già e exp che è una funzione della libreria standard del C. Quest'ultima è una funzione matematica che calcola e^x.

Come si può vedere nell'esempio, per invocare exp è stato necessario specificare il nome e il parametro x di cui deve calcolare la potenza racchiuso tra due parentesi. Nel far questo, quando il nostro programma raggiunge la riga 8:

y = exp(x);

cede il controllo di esecuzione alla funzione. Da questo momento in poi, la funzione calcolerà il risultato per poi cedere nuovamente il controllo al nostro programma che ne raccoglie il risultato. In gergo tecnico si dice che la funzione exp restituisce il risultato, che poi viene memorizzato nella variabile y.

Nota: Attenzione al programma di esempio di sopra. Se provate a compilare il programma utilizzando gcc otterrete degli errori di compilazione. Infatti, per poter compilare un programma C che usa la libreria matematica è necessario fornire l'opzione di compilazione -lm. Ciò non è necessario per il compilatore C di visual studio.

Non necessariamente una funzione deve restituire un risultato. Vi sono funzioni che svolgono un compito e non restituiscono nulla. In tal caso si parla di procedure. In alcuni linguaggi di programmazione procedure e funzioni sono trattate diversamente. In C, invece, procedure e funzioni sono trattate allo stesso modo.

Il valore restituito da una funzione può anche essere ignorato. Ad esempio, avremmo potuto scrivere:

exp(y);

Tuttavia un'espressione del genere avrebbe avuto poco senso. La printf restituisce anch'essa un valore (che finora abbiamo sempre ignorato). Essa fornisce in output, infatti, il numero di caratteri che ha stampato a schermo. Ad esempio:

1
2
3
4
5
6
7
#include <stdio.h>

int main() {
    int numero_caratteri = printf("TEST\n");
    printf("Numero di caratteri stampati: %d\n", numero_caratteri);
    return 0;
}

Il meccanismo di invocazione di funzione o chiamata a funzione prevede i seguenti passaggi:

  1. La funzione che invoca, detta chiamante, invoca la funzione desiderata specificandone il nome e i parametri tra parentesi.
  2. Il controllo di esecuzione viene ceduto alla funzione chiamata.
  3. Le istruzioni contenute nel corpo della funzione chiamata vengono eseguite.
  4. Una funzione può restituire un risultato o meno. Nel caso in cui lo restituisca, il chiamante può recuperare il risultato salvandolo in una variabile.
  5. Al termine dell'esecuzione delle istruzioni della funzione chiamata, l'esecuzione riprende dal punto successivo a quello in cui la funzione è stata invocata.

Questo meccanismo di invocazione può essere esteso anche alle funzioni chiamate, cioè le funzioni chiamate possono a loro volta invocare altre funzioni. Questo è un punto fondamentale che vedremo più avanti nel corso delle lezioni.

In sintesi

In questa lezione abbiamo visto che:

  • Il linguaggio C mette a disposizione le funzioni per modularizzare i nostri programmi.
  • Le funzioni possono essere invocate specificando il nome e i parametri tra parentesi.
  • Le funzioni, possono, inoltre restituire un risultato che può essere anche ignorato.
  • Tipicamente, le funzioni che non restituiscono un risultato prendono il nome di procedure. Mentre in altri linguaggi funzioni e procedure sono trattate diversamente, il C le tratta allo stesso modo.
  • Le funzioni chiamate possono, a loro volta, invocare altre funzioni.

Nella prossima lezione vedremo come creare funzioni definite dall'utente.