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:
- Efficienza: si evita di copiare l'intera struct
- 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 normalepuntatore_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
- Definiamo una struct
struct dipendentecon tre campi: nome, età e stipendio - La funzione
aumentaStipendioaccetta un puntatore astruct dipendentee usa l'operatore->per accedere al campo stipendio - La funzione
stampaDipendenteutilizza lo stesso operatore per leggere i valori della struct - In
main(), creiamo unastruct dipendentee 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