Le Funzioni

Una funzione é un blocco di codice contenente istruzioni atte a svolgere uno specifico obbiettivo computazionale.

Offrono tutta una serie di vantaggi di tipo pratico, come ad esempio:

  • Modularità: con le funzioni in codice può essere scomposto in più piccole unità di elaborazione che agiscono come moduli che hanno il compito di svolgere un isolato scopo, rendendo nel contempo più semplice e leggibile il codice.
  • Riutilizzo: le funzioni possono essere scritte una sola volta ed invocate infinite volte, rendendo più snello il codice.
  • Occultamento dei dati: le funzioni consentono di definire dei dati che rimangono accessibili solo all’interno della funzione stessa. Consentono inoltre di utilizzare gli stessi identificatori di variabili o costanti già utilizzate all’esterno senza che questo influisca sul funzionamento del programma (scope).

Definizione di una funzione

tipo nome_funzione(argomento1,argomento2){
    dichiarazioni;
    espressioni;
    return;
}

Il listato sopra evidenziato descrive la struttura sintattica di una funzione. Essa é caratterizzata da un tipo di valore di ritorno (int, double, long, etc etc), un proprio nome identificativo, una coppia di parentesi aperte che possono contenere gli argomenti passati all’interno della funzione, e la parola chiave “return” che restituisce il dato elaborato alla funzione chiamante. Tuttavia non é obbligatorio che la funzione restituisca qualcosa, così come non é obbligatorio che abbia dei valori in ingresso.

Invocazione di una funzione

Il procedimento con cui una funzione viene eseguita é definito “invocazione” e si esprime in questo modo:

nome_funzione(argomento1, argomento2);

Viene semplicemente scritto il nome di una funzione precedentemente dichiarata nel punto del codice in cui vogliamo che restituisca il risultato o produca i suoi effetti.

Dichiarazione di una funzione

Una funzione, per poter essere eseguita deve essere stata precedentemente dichiarata. Questo può avvenire in due modi corretti:

  • Può essere scritta per esteso prima della funzione principale main;
  • Può essere solo dichiarata attraverso un prototipo prima della funzione principale main, per poi essere scritta per esteso successivamente a questa.

La ragione di ciò risiede nel fatto che il compilatore deve “conoscere” la funzione che si sta invocando, pena la dichiarazione implicita, attraverso la quale il compilatore assume che la funzione invocata sia di tipo int e priva di argomenti, dando luogo a risultati non definiti.

I prototipi di funzione

Come già accennato é possibile indicare al compilatore soltanto la dichiarazione della funzione, esplicitandone il corpo successivamente alla funzione principale main. Tale procedura é detta prototipo.

#include <stdio.h>
#include <stdlib.h>

int radice_quadrata(int numero);

int main(void){
    int numero = 4
    int radice = radice_quadrata(numero);
    printf(“La radice quadrata di %d é %d”, numero, radice);
    return (EXIT_SUCCESS);
}

int radice_quadrata(int numero){
    radice = sqrt(numero);
    return radice;
}

NB: Per dichiarare un prototipo di funzione che non accetta argomenti utilizzare sempre la keyword void es: int nome_funzione(void).

Parametri di funzione

Come é noto, le variabili rappresentano posizioni di memoria contenenti dei dati, tuttavia, quando vengono passate come argomenti ad un funzione, questi diventano delle copie, per cui é possibile operare su di essi senza il timore di alterare le posizioni di memoria a cui essi si riferiscono. Tale modalità é detta per valore (by value).

Promozione degli argomenti

Quando si invoca una funzione é possibile che gli argomenti ad essa passati non concordino con il tipo previsto in sede di definizione della funzione stessa. In tali circostanze assisteremo ad una conversione o ad una promozione del dato in quello previsto dal prototipo o dalla funzione. Ciò può dare luogo ad una perdita del dato o a comportamenti non definiti.

Passaggio di parametri array alla funzione

Una funzione può operare anche su degli oggetti di tipo array, sia monodimensionali che multidimensionali, attraverso le seguenti sintassi:

tipo nome_funzione( tipo nome_identificatore [] );

Occorre però tenere presente che, quando si opera su un array all’interno di una funzione si sta operando però su un puntatore all’array stesso, cosa che può dare luogo a difficoltà di cui l’uso dell’operatore sizeof può rappresentare un esempio. Utilizzando infatti sizeof per conoscere le dimensioni di un array all’interno di una funzione, ci verrà restituita la lunghezza del puntatore, corrispondente a 4 byte.

Array multidimensionali come parametri di funzione

Per passare ad una funzione un array multidimensionale si utilizza la seguente sintassi:

//Array bidimensionale
tipo nome_funzione( tipo nome_identificatore [] [NUMERO_COLONNE] );

//Array tridimensionale
tipo nome_funzione( tipo nome_identificatore [] [NUMERO_COLONNE] [NUMERO_PAGINE]);

La regola generale é che prima dimensione può sempre essere omessa, mentre vanno indicate tutte le dimensioni successive.

VLA come parametri di funzione

Nel caso di passaggio di VLA come argomenti, va invece utilizzata la seguente sintassi:

//Solo nei prototipi
//l é la variabile di lunghezza
tipo nome_funzione( int l, tipo identificatore_array [*] );

//Definizione della funzione
//l viene inserita nelle quadre
tipo nome_funzione( int l, tipo identificatore_array [l] ); 

Vengono sempre passati almeno 2 parametri, dove il primo rappresenta la variabile che definisce la lunghezza del array, ed il secondo il nome dell’array avente tra parentesi quadre il primo argomento. Nei prototipi quest’ultima accortezza può essere sostituita con il carattere * posto nelle parentesi quadre.

Passaggio di un numero indeterminato di argomenti ad una funzione

Fino adesso abbiamo sempre passato ad una funzione un numero predefinito di argomenti, tuttavia può accadere che tale numero non sia noto, o quanto meno non sia ancora. Utilizziamo a questo scopo la libreria <stdarg.h> che fornisce tipi e macro utili a gestire le funzioni con un numero indeterminato di argomenti. Vediamo come:

  1. Inserire il file header <stdarg.h>
  2. Definire un funzione avente almeno un parametro, seguito da tre puntini di sospensione …
  3. Dichiarare nella funzione una variabile di tipo va_list (non int, double, float etc etc)
  4. Utilizzare la macro va_start usando come primo parametro la variabile di tipo va_list, e come secondo, la variabile che abbiamo utilizzato come primo argomento della funzione
  5. Utilizzare la macro va_arg fornendo come primo parametro sempre la variabile di tipo va_list, ma come secondo parametro il tipo di dato da processare
  6. Chiudere con la macro va_end, fornendo come parametro sempre va_list, per chiudere il costrutto.
#include <stdarg.h> //1 Da mettere nel header
int nome_funzione(int numero, ...){ //2 Aggiungo … nei parametri
    va_list argomenti; //3 Variabile argomenti
    va_start(argomenti, numero); //4 Inizializzo argomenti
    int argomento1 = va_arg(argomenti, int); //5 Ricavo primo argomento
    int argomento2 = va_arg(argomenti, int); //7 Ricavo secondo argomento
    va_end(argomenti); //8 Chiudo e clean up
}

L’istruzione return

Con l’istruzione return si termina l’esecuzione di una funzione riportando il controllo alla funzione chiamante. Si può altresì utilizzare per restituire alla funzione chiamante il risultato delle operazioni eseguite dalla funzione, che deve essere dello stesso tipo definito dalla funzione stessa (pena la conversione con possibile perdita di dati), tuttavia se una funzione é di tipo void, può anche non restituire alcun valore, e l’istruzione return può essere posta in qualunque punto della funzione, o, in alternativa, attendere l’esecuzione della funzione raggiunga il termine rappresentato dalla }.

Tipo FunzioneTipo di returnComportamento
non-voidno-returnnon definito
non-voidvoidnon definito
voidnon-voidnon definito

Le funzioni _Noreturn

A partire dallo standard C11 é possibile utilizzare lo specificatore _Noreturn per indicare che una funzione non restituisce alcun dato. Questo si diversifica dall’indicare una funzione void in quanto in questo caso non viene restituito il controllo alla funzione chiamante.

Noreturn void ciao(){
    printf(“Ciao”);
    exit(0);
}

Le funzioni inline

Lo standard C99 rende possibile definire funzioni “in linea” evitando l’overhead della macchina, ossia tutte quelle operazioni legate al passaggio di controlli e dati determinati dall’esecuzione di una funzione. E’ vantaggioso nel caso di piccole funzioni.

static inline int ciao { printf(“ciao”); }

Ricorsione

Con il termine ricorsione si intende il procedimento con cui viene reiterata l’invocazione di una funzione dall’interno della funzione stessa. Essa può essere anche un efficace procedura di iterazione dei dati. E, per evitare cicli infiniti, si definisce nella ricorsione un cosidetto “caso base” che rappresenta il punto di uscita della ricorsione stessa, ed un “passo ricorsivo” all’interno del quale la funzione continuerà ad essere invocata.

voidcountdown(numero){
    if (numero == 0){ // Caso base
    return;
}
printf(“%n”, numero);
countdown(numero); // Passo ricorsivo

I vantaggi offerti dalla ricorsione sono dati dalla leggibilità del codice e dalla sua agevolezza, per contro, l’utilizzo della ricorsione incrementa la quantità di risorse richieste alla macchina indi per cui può appesantire i processi.

La funzione main

Abbiamo finora utilizzato la funzione main senza preoccuparci troppo del suo significato che tuttavia può risultare molto interessante qualora desiderassimo che i nostri programmi abbiamo un utilità inter-operativa. Dobbiamo innanzitutto dire che la funzione main é invocata nel momento in cui il programma é avviato e a questa possono essere passati dei parametri da riga di comando. La sintassi osservata é la seguente:

int main(int argc, char *argv[]) { /…/ }

Qualora volessimo eseguire un programma passando a questo degli argomenti da riga di comando, sarà possibile fornire alla funzione main qualunque argomento, agendo sull’argomento argc (arguments counter, il numero di argomenti che stiamo passando) e *argv ossia un vettore contenente i parametri passati sotto forma di stringa. Alla posizione 0 di questo array avremo il nome del programma e dalla 1 in avanti gli argomenti passati. Esempio:

iMac:~ user$ chmod 755 index.html

In questo esempio possiamo osservare il lancio da terminale di un comando con il quale si modificano i permessi di un file, dove “chmod” é il nome del programma, mentre “755” e “index.html” sono gli argomenti, contenuti rispettivamente in argv[1] e argv[2].

Luca Scandroglio

Sono un consulente tecnico informatico, un web designer e uno sviluppatore italiano. Aiuto le aziende a dotarsi degli strumenti tecnologici e digitali per superare le sfide del mercato di oggi.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.