Ricerca…
introduzione
Un puntatore è un indirizzo che fa riferimento a una posizione in memoria. Sono comunemente usati per consentire a funzioni o strutture di dati di conoscere e modificare la memoria senza dover copiare la memoria a cui si fa riferimento. I puntatori sono utilizzabili sia con tipi primitivi (built-in) che definiti dall'utente.
I puntatori utilizzano "dereferenzia" *
, "indirizzo di" &
, e "freccia" ->
operatori. Gli operatori '*' e '->' sono usati per accedere alla memoria puntata, e l'operatore &
viene usato per ottenere un indirizzo in memoria.
Sintassi
- <Tipo di dati> * <Nome variabile>;
- <Tipo di dati> * <Nome variabile> = & <Nome variabile dello stesso tipo di dati>;
- <Tipo di dati> * <Nome variabile> = <Valore dello stesso tipo di dati>;
- int * foo; // Un puntatore che punta a un valore intero
- int * bar = & myIntVar;
- long * bar [2];
- long * bar [] = {& myLongVar1, & myLongVar2}; // Uguale a: long * bar [2]
Osservazioni
Essere consapevoli dei problemi quando si dichiarano più puntatori sulla stessa linea.
int* a, b, c; //Only a is a pointer, the others are regular ints.
int* a, *b, *c; //These are three pointers!
int *foo[2]; //Both *foo[0] and *foo[1] are pointers.
Nozioni di base del puntatore
Nota: in tutti i seguenti casi, si presume l'esistenza di nullptr
costante C ++ 11. Per le versioni precedenti, sostituire nullptr
con NULL
, la costante utilizzata per riprodurre un ruolo simile.
Creazione di una variabile puntatore
Una variabile puntatore può essere creata usando la sintassi specifica *
, ad es int *pointer_to_int;
.
Quando una variabile è di tipo puntatore ( int *
), contiene solo un indirizzo di memoria. L'indirizzo di memoria è la posizione in cui sono memorizzati i dati del tipo sottostante ( int
).
La differenza è evidente quando si confronta la dimensione di una variabile con la dimensione di un puntatore allo stesso tipo:
// Declare a struct type `big_struct` that contains
// three long long ints.
typedef struct {
long long int foo1;
long long int foo2;
long long int foo3;
} big_struct;
// Create a variable `bar` of type `big_struct`
big_struct bar;
// Create a variable `p_bar` of type `pointer to big_struct`.
// Initialize it to `nullptr` (a null pointer).
big_struct *p_bar0 = nullptr;
// Print the size of `bar`
std::cout << "sizeof(bar) = " << sizeof(bar) << std::endl;
// Print the size of `p_bar`.
std::cout << "sizeof(p_bar0) = " << sizeof(p_bar0) << std::endl;
/* Produces:
sizeof(bar) = 24
sizeof(p_bar0) = 8
*/
Prendendo l'indirizzo di un'altra variabile
I puntatori possono essere assegnati tra loro come normali variabili; in questo caso, è l' indirizzo di memoria che viene copiato da un puntatore a un altro, non i dati effettivi a cui punta un puntatore.
Inoltre, possono assumere il valore nullptr
che rappresenta una posizione di memoria nulla. Un puntatore uguale a nullptr
contiene una posizione di memoria non valida e quindi non fa riferimento a dati validi.
È possibile ottenere l'indirizzo di memoria di una variabile di un determinato tipo mediante il prefisso della variabile con l' indirizzo dell'operatore &
. Il valore restituito da &
è un puntatore al tipo sottostante che contiene l'indirizzo di memoria della variabile (che è un dato valido finché la variabile non esce dall'ambito ).
// Copy `p_bar0` into `p_bar_1`.
big_struct *p_bar1 = p_bar0;
// Take the address of `bar` into `p_bar_2`
big_struct *p_bar2 = &bar;
// p_bar1 is now nullptr, p_bar2 is &bar.
p_bar0 = p_bar2;
// p_bar0 is now &bar.
p_bar2 = nullptr;
// p_bar0 == &bar
// p_bar1 == nullptr
// p_bar2 == nullptr
In contrasto con i riferimenti:
- l'assegnazione di due puntatori non sovrascrive la memoria a cui fa riferimento il puntatore assegnato;
- i puntatori possono essere nulli.
- l' indirizzo dell'operatore è richiesto esplicitamente.
Accedere al contenuto di un puntatore
Come richiedere un indirizzo &
, oltre all'accesso al contenuto, è necessario l'utilizzo dell'operatore di dereferenza *
come prefisso. Quando un puntatore viene dereferenziato, diventa una variabile del tipo sottostante (in realtà, un riferimento ad esso). Può quindi essere letto e modificato, se non const
.
(*p_bar0).foo1 = 5;
// `p_bar0` points to `bar`. This prints 5.
std::cout << "bar.foo1 = " << bar.foo1 << std::endl;
// Assign the value pointed to by `p_bar0` to `baz`.
big_struct baz;
baz = *p_bar0;
// Now `baz` contains a copy of the data pointed to by `p_bar0`.
// Indeed, it contains a copy of `bar`.
// Prints 5 as well
std::cout << "baz.foo1 = " << baz.foo1 << std::endl;
La combinazione di *
e l'operatore .
è abbreviato da ->
:
std::cout << "bar.foo1 = " << (*p_bar0).foo1 << std::endl; // Prints 5
std::cout << "bar.foo1 = " << p_bar0->foo1 << std::endl; // Prints 5
Dereferenziare i puntatori non validi
Quando si dereferenzia un puntatore, è necessario assicurarsi che punti a dati validi. Dereferenziare un puntatore non valido (o un puntatore nullo) può portare alla violazione di accesso alla memoria, o leggere o scrivere dati inutili.
big_struct *never_do_this() {
// This is a local variable. Outside `never_do_this` it doesn't exist.
big_struct retval;
retval.foo1 = 11;
// This returns the address of `retval`.
return &retval;
// `retval` is destroyed and any code using the value returned
// by `never_do_this` has a pointer to a memory location that
// contains garbage data (or is inaccessible).
}
In tale scenario, g++
e clang++
rilasciano correttamente gli avvertimenti:
(Clang) warning: address of stack memory associated with local variable 'retval' returned [-Wreturn-stack-address]
(Gcc) warning: address of local variable ‘retval’ returned [-Wreturn-local-addr]
Quindi, bisogna fare attenzione quando i puntatori sono argomenti di funzioni, in quanto potrebbero essere nulli:
void naive_code(big_struct *ptr_big_struct) {
// ... some code which doesn't check if `ptr_big_struct` is valid.
ptr_big_struct->foo1 = 12;
}
// Segmentation fault.
naive_code(nullptr);
Operazioni di puntamento
Ci sono due operatori per i puntatori: Indirizzo-di operatore (&): restituisce l'indirizzo di memoria del suo operando. Operatore Contents-of (Dereference) (*): restituisce il valore della variabile che si trova all'indirizzo specificato dal suo operatore.
int var = 20;
int *ptr;
ptr = &var;
cout << var << endl;
//Outputs 20 (The value of var)
cout << ptr << endl;
//Outputs 0x234f119 (var's memory location)
cout << *ptr << endl;
//Outputs 20(The value of the variable stored in the pointer ptr
L'asterisco (*) viene utilizzato nel dichiarare un puntatore con lo scopo semplice di indicare che si tratta di un puntatore. Non confondere questo con l'operatore di dereferenziazione , che viene utilizzato per ottenere il valore situato all'indirizzo specificato. Sono semplicemente due cose diverse rappresentate con lo stesso segno.
Puntatore aritmetico
Incrementa / Decrementa
Un puntatore può essere incrementato o decrementato (prefisso e postfix). L'incremento di un puntatore fa avanzare il valore del puntatore all'elemento nell'array di un elemento oltre l'elemento correntemente puntato. Il decremento di un puntatore lo sposta sull'elemento precedente dell'array.
L'aritmetica del puntatore non è consentita se il tipo a cui punta il puntatore non è completo. void
è sempre un tipo incompleto.
char* str = new char[10]; // str = 0x010
++str; // str = 0x011 in this case sizeof(char) = 1 byte
int* arr = new int[10]; // arr = 0x00100
++arr; // arr = 0x00104 if sizeof(int) = 4 bytes
void* ptr = (void*)new char[10];
++ptr; // void is incomplete.
Se viene incrementato un puntatore all'elemento end, il puntatore punta a un elemento oltre la fine dell'array. Tale puntatore non può essere dereferenziato, ma può essere decrementato.
L'incremento di un puntatore all'elemento one-past-end nell'array o il decremento di un puntatore al primo elemento di un array produce un comportamento non definito.
Un puntatore a un oggetto non matrice può essere trattato, ai fini dell'aritmetica del puntatore, come se fosse una matrice di dimensione 1.
Addizione / sottrazione
I valori interi possono essere aggiunti ai puntatori; agiscono come incrementi, ma per un numero specifico anziché per 1. I valori interi possono essere sottratti dai puntatori, agendo come decremento del puntatore. Come con l'incremento / decremento, il puntatore deve puntare a un tipo completo.
char* str = new char[10]; // str = 0x010
str += 2; // str = 0x010 + 2 * sizeof(char) = 0x012
int* arr = new int[10]; // arr = 0x100
arr += 2; // arr = 0x100 + 2 * sizeof(int) = 0x108, assuming sizeof(int) == 4.
Differenziamento del puntatore
La differenza tra due puntatori allo stesso tipo può essere calcolata. I due puntatori devono essere all'interno dello stesso oggetto matrice; risultati del comportamento altrimenti indefiniti.
Dato due puntatori P
e Q
nello stesso array, se P
è l' i
-esimo elemento dell'array, e Q
è il j
-esimo elemento, allora P - Q
è i - j
. Il tipo del risultato è std::ptrdiff_t
, da <cstddef>
.
char* start = new char[10]; // str = 0x010
char* test = &start[5];
std::ptrdiff_t diff = test - start; //Equal to 5.
std::ptrdiff_t diff = start - test; //Equal to -5; ptrdiff_t is signed.