Strutture e Funzioni in linguaggio C

Abbiamo visto che una struttura dati può essere usata per definire un nuovo tipo di dato. A partire da ciò possiamo usare questo nuovo tipo di dato per passare parametri ad una funzione sotto forma di strutture dati.

Usare le strutture dati in questo modo risulta utile in molti casi in cui bisogna passare informazioni correlate logicamente ad una funzione.

Inoltre, vedremo come restituire una struttura dati come valore di ritorno di una funzione.

Strutture come Argomenti di Funzione

Nella lezione precedente abbiamo visto come definire strutture come tipo di dato.

Dal momento che un tipo di dato può essere una struct, possiamo usare tale tipo come argomento di una funzione.

Ad esempio, possiamo definire una struttura dati che rappresenta un punto in un piano cartesiano:

struct punto {
    int x;
    int y;
};

e definire una funzione che accetta come argomento una variabile di tipo struct punto. Possiamo definire una funzione che calcola la distanza euclidea del punto dall'origine del piano:

#include <math.h>

double distanza(struct punto p) {
    return sqrt(p.x * p.x + p.y * p.y);
}

In questo esempio abbiamo usato una struttura etichettata. Avremmo anche potuto definire un nuovo tipo attraverso typedef:

typedef struct {
    double x;
    double y;
} punto;

double distanza(punto p) {
    return sqrt(p.x * p.x + p.y * p.y);
}

La cosa principale da tenere a mente quando si usano le struct come argomento di funzione è che, quando si passa una variabile di tipo struct come argomento essa sarà sempre passata per copia. In altre parole, se invochiamo la funzione distanza in questo modo:

struct punto x = { 3.0, 4.0 };

double d = distanza(x);

la variabile x sarà copiata nella variabile p all'interno della funzione distanza.

Finché si tratta di strutture dati di dimensioni piccole, questo non è un problema. Tuttavia, se la struttura inizia ad essere di grandi dimensioni il passaggio per copia può risultare oneroso. Questa problematica viene tenuta nascosta dal compilatore, tuttavia quando si cerca di ottimizzare un programma bisogna tenerne conto.

Ad esempio, supponiamo di avere una struttura dati di questo tipo:

struct vettore {
    float elementi[1024];
};

Questa struttura è composta da ben 1024 elementi di tipo float. Proviamo a creare una funzione che calcola, ad esempio, la media di tutti gli elementi della struttura:

float media(struct vettore v) {
    float somma = 0.0;
    for (int i = 0; i < 1024; i++) {
        somma += v.elementi[i];
    }
    return somma / 1024.0F;
}

Per come è definita, questa funzione copia gli elementi della struttura ogniqualvolta viene invocata. Ciò significa che ogni invocazione comporta la copia di 1024 elementi float da un punto di memoria all'altro. Questo è un costo molto alto, soprattutto se la funzione viene invocata molte volte.

Per questo motivo, il passaggio di strutture dati come argomenti ad una funzione va usato con parsimonia. In altri casi, come vedremo in futuro, è meglio passare le strutture dati ad una funzione per riferimento. Per farlo, tuttavia, dobbiamo prima studiare in una prossima lezione i puntatori a struct.

Definizione

Passaggio di struct a funzione

Una funzione in linguaggio C può accettare in ingresso parametri di tipo struttura dati, struct.

Le sintassi per specificare parametri di tipo struct sono:

  • struct etichettate:

    struct nome_struct {
        /* ... */
    };
    
    tipo_ritorno funzione(struct nome_struct parametro_1, struct nome_struct parametro_2) {
        /* ... */
    }
    
  • typedef:

    typedef struct {
        /* ... */
    } nome_struct;
    
    tipo_ritorno funzione(nome_struct parametro_1, nome_struct parametro_2) {
        /* ... */
    }
    

Quando si passa un argomento di tipo struct ad una funzione esso sarà sempre passato per copia. Questo significa che la funzione riceverà una copia dei dati della struttura, e non un riferimento alla struttura stessa.

Strutture come valori di ritorno di funzione

In questa lezione abbiamo visto come passare una struttura dati come argomento di una funzione. Possiamo fare lo stesso anche per i valori di ritorno di una funzione.

Ad esempio, data la struttura che rappresenta un punto in un piano cartesiano:

struct punto {
    double x;
    double y;
};

possiamo definire una funzione che calcola il punto medio tra due punti:

struct punto punto_medio(struct punto p1, struct punto p2) {
    struct punto p;
    p.x = (p1.x + p2.x) / 2.0;
    p.y = (p1.y + p2.y) / 2.0;
    return p;
}

In questo esempio, la funzione crea dapprima la variabile p che rappresenta il nostro valore di ritorno. Successivamente, calcola i valori di x e y e li assegna alla variabile p. Infine, la funzione ritorna il valore di p.

Nel ritornare la variabile struct p, la funzione copia tutti i dati della struttura. Quindi, ad esempio, se invochiamo la funzione in questo modo:

struct punto p1 = { 1.0, 2.0 };
struct punto p2 = { 3.0, 4.0 };

struct punto x = punto_medio(p1, p2);

quello che accade è che il risultato della funzione verrà copiato all'interno di x. Quindi anche per il valore di ritorno di una funzione valgono le stesse considerazioni sulle performance. Se la struttura è di grandi dimensioni restituire un valore di quel tipo può risultare molto oneroso.

In Sintesi

In questa lezione abbiamo studiato il passaggio di una struttura dati come argomento di una funzione. Abbiamo visto che in generale il passaggio avviene per copia.

Inoltre, abbiamo visto come passare una struttura dati come valore di ritorno di una funzione. Anche in questo caso, la restituzione avviene per copia.

Tale passaggio può risultare molto oneroso nei casi in cui la struttura dati da passare sia di grandi dimensioni. In questi casi, è meglio passare la struttura dati per riferimento. Per farlo, tuttavia, dobbiamo prima studiare i puntatori a struct come vedremo nelle lezioni future.