Puntatori

Indice

Definizione

Una variabile puntatore contiene l’indirizzo di memoria di un oggetto (variabile o costante)

Sintassi

tipo *nomeVariabile;

La definizione di ogni puntatore richiede un ‘*’

int x, y;
int *p, *q;

x e y sono variabili di tipo int p e q sono variabili di tipo puntatore-a-int

La definizione di una variabile puntatore riserva memoria solo per contenere l’indirizzo di un oggetto, ma non riserva memoria per l’oggetto puntato: il puntatore deve essere inizializzato o assegnato successivamente.

La definizione di una variabile puntatore riserva memoria solo per contenere l’indirizzo di un oggetto, ma non riserva memoria per l’oggetto puntato: il puntatore deve essere inizializzato o assegnato successivamente

Operatore &

L’operatore di indirizzo ‘&’ (“ampersand”) estrae l’indirizzo di memoria di un oggetto (variabile, costante, array, ecc.)

Essendo un puntatore una variabile di tipo numerico, l’assegnazione avviene con l’operatore ‘=’ Il valore da assegnare a una variabile puntatore deve essere un indirizzo di memoria:

int *p, *q;
int x, vet[10];

p = &x; // assegna a p l’indirizzo di x
q = vet; // assegna a q l’indirizzo di vet[0]

Passaggio parametri per indirizzo

In C non esiste il passaggio per riferimento, ma lo si può simulare passando per valore alla funzione l’indirizzo del dato (che deve essere una variabile, non può essere il risultato di un calcolo perché non avrebbe un indirizzo) da far pervenire alla funzione.

#include <stdio.h>
void dodici(int *);
int main()
{
    int x=2;
    dodici(&x);
    printf("%d\n", x); // stampa 12
    return EXIT_SUCCESS;
}
void dodici(int *p)
{
    *p = 12;
}

Il passaggio per riferimento può essere utile anche per permettere ad una funzione di restituire più di un valore: nell’elenco degli argomenti si passano gli indirizzi di tutte le variabili a cui la funzione assegnerà risultati

Nella scanf le variabili scalari sono precedute da & proprio perché se ne passa l’indirizzo, in quanto devono essere assegnate dalla funzione

Per passare un vettore come argomento, si indica il suo nome senza parentesi quadre:

int vettore[N]={1,2,3};
float x;
x = media(vettore);

Poiché il nome di un vettore è l’indirizzo di memoria del suo primo elemento, un vettore viene sempre passato per indirizzo

Passaggio di Struct per Indirizzo in C

In C, passare una struct per indirizzo (invece che per valore) è una pratica comune che offre principalmente due vantaggi:

  1. Efficienza: si evita di copiare l'intera struct
  2. Modificabilità: permette di modificare la struct originale

Queste caratteristiche sono particolarmente importanti in contesti in cui le performance sono cruciali (Sistemi Operativi, Videogames) ed in tutte le applicazioni moderne in cui si sfruttano le moderne architetture parallelizzando le attività. In caso vi siano più thread ad eseguire un programma è importante avere la possibilità di modificare la stessa struttura dati e non avere duplicati in caso sia necessario aggiornare la stessa informazione.

Sintassi di Base

Quando si passa una struct per indirizzo, si utilizza l'operatore -> per accedere ai suoi campi, invece del tradizionale . usato quando si lavora con la struct direttamente.

Sintassi:

  • struttura.campo → per accedere al campo di una struct normale
  • puntatore_a_struttura->campo → per accedere al campo tramite puntatore

Esempio Completo

Ecco un esempio che mostra come definire, passare e manipolare una struct tramite puntatore:

#include <stdio.h>
#include <string.h>

// Definizione della struct
struct dipendente {
    char nome[50];
    int eta;
    float stipendio;
};

// Funzione che riceve un puntatore a Dipendente
void aumentaStipendio(struct dipendente *dip, float percentuale) {
    // Utilizzo dell'operatore -> per accedere ai campi
    dip->stipendio *= (1 + percentuale/100);

    // Equivalente a (*dip).stipendio *= (1 + percentuale/100);
}

// Funzione che stampa i dati del dipendente.
// Uso const per segnalare che i valori non verranno modificati dalla funzione
void stampaDipendente(const struct dipendente *dip) {
    printf("Nome: %s\n", dip->nome);
    printf("Età: %d\n", dip->eta);
    printf("Stipendio: %.2f €\n", dip->stipendio);
}

int main() {
    // Creazione e inizializzazione della struct
    struct dipendente impiegato;
    strcpy(impiegato.nome, "Mario Rossi");
    impiegato.eta = 35;
    impiegato.stipendio = 2000.0;

    printf("Prima dell'aumento:\n");
    stampaDipendente(&impiegato);

    // Passaggio per indirizzo alla funzione
    aumentaStipendio(&impiegato, 10);

    printf("\nDopo l'aumento del 10%%:\n");
    stampaDipendente(&impiegato);

    return 0;
}

Spiegazione

  1. Definiamo una struct struct dipendente con tre campi: nome, età e stipendio
  2. La funzione aumentaStipendio accetta un puntatore a struct dipendente e usa l'operatore -> per accedere al campo stipendio
  3. La funzione stampaDipendente utilizza lo stesso operatore per leggere i valori della struct
  4. In main(), creiamo una struct dipendente e passiamo il suo indirizzo alle funzioni tramite l'operatore &

Note Importanti

  • L'operatore -> è una scorciatoia per la notazione (*ptr).campo
  • Quando il puntatore è costante const struct dipendente *dip, non si possono modificare i campi ma solo leggerli
  • Il passaggio per indirizzo è particolarmente utile per struct grandi, evitando costose operazioni di copia della memoria

Validate