ITA - Appunti Cpp 012 - Puntatori
August 2024 (2882 Words, 17 Minutes)
Un puntatore è una variabile che contiene l’indirizzo di memoria di un’altra variabile. Semplificando, invece di contenere direttamente un valore, un puntatore “punta” alla posizione in memoria dove è memorizzato quel valore.
Dichiarazione di un Puntatore
Per dichiarare un puntatore, si utilizza l’operatore *
:
int *p; // dichiara un puntatore a un intero
In questo esempio, p
è una variabile che può contenere l’indirizzo di memoria di una variabile di tipo int
.
Assegnazione di un Indirizzo a un Puntatore
Per assegnare un indirizzo a un puntatore, si utilizza l’operatore di indirizzo &
:
int a = 10;
int *p = &a; // p contiene l'indirizzo di a
Dove p
contiene l’indirizzo di memoria della variabile a
.
Accesso al Valore Puntato
Per accedere al valore memorizzato all’indirizzo a cui un puntatore punta, si utilizza l’operatore di dereferenziazione *
:
int a = 10;
int *p = &a;
int b = *p; // b è uguale a 10
Dove *p
dà accesso al valore di a
, quindi b
sarà uguale a 10.
Puntatori e Array
Il nome di un array è un puntatore costante al primo elemento dell’array:
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // p è uguale a &arr[0]
printf("%d
", *(p + 1)); // Output: 2
In questo esempio, p
punta al primo elemento dell’array arr
. L’espressione *(p + 1)
accede al secondo elemento dell’array.
Puntatori a Puntatori
È possibile avere puntatori che puntano ad altri puntatori. Questo è utile in varie situazioni, come la creazione di strutture dati complesse. Ecco un esempio:
int a = 10;
int *p = &a;
int **pp = &p;
printf("%d
", **pp); // Output: 10
In questo esempio, pp
è un puntatore a p
, che a sua volta è un puntatore a a
.
Allocazione Dinamica della Memoria
I puntatori sono essenziali per l’allocazione dinamica della memoria. Le funzioni :
malloc
,calloc
,realloc
efree
in Cnew
edelete
in C++ permettono di allocare e deallocare memoria durante l’esecuzione del programma.
Ecco un esempio in C:
int *p = (int*) malloc(5 * sizeof(int)); // alloca un array di 5 interi
if (p != NULL) {
for (int i = 0; i < 5; i++) {
p[i] = i;
}
}
free(p); // dealloca la memoria
Esempio in C++:
int *p = new int[5]; // alloca un array di 5 interi
for (int i = 0; i < 5; i++) {
p[i] = i;
}
delete[] p; // dealloca la memoria
Funzioni e Puntatori
Puntatori come parametri
I puntatori possono essere passati alle funzioni per consentire alle funzioni di modificare i valori delle variabili passate.
Ecco un esempio:
void increment(int *p) {
(*p)++;
}
int main() {
int a = 10;
increment(&a);
printf("%d\n", a); // Output: 11
return 0;
}
In questo esempio, la funzione increment
prende un puntatore a un int
e incrementa il valore puntato.
Puntatori a Funzioni
Operazioni con puntatori a funzioni sono consentite, l’uso tipico di questa proprietà del linguaggio è passare una funzione come argomento a un’altra funzione.
I puntatori a funzioni sono dichiarati con la stessa sintassi di una dichiarazione di funzione regolare,
tranne per il fatto che il nome della funzione è racchiuso tra parentesi ()
e un *
è inserito prima del nome:
// puntatore a funzioni
#include <iostream>
using namespace std;
int addizione (int a, int b)
{ return (a+b); }
int sottrazione (int a, int b)
{ return (a-b); }
int operazione (int x, int y, int (*chiamata)(int,int))
{
int g;
g = (*chiamata)(x,y);
return (g);
}
int main ()
{
int m,n;
int (*meno)(int,int) = sottrazione;
m = operazione (7, 5, addizione);
n = operazione (20, m, sottrazione);
cout <<n;
return 0;
}
Nell’esempio precedente, meno
è un puntatore a una funzione che ha due parametri di tipo int
, viene inizializzato direttamente per puntare alla funzione sottrazione
.
Valori per Riferimento
Un riferimento è un alias per una variabile già esistente; un riferimento è dichiarato utilizzando l’operatore &
.
Diversamente dai puntatori, i riferimenti devono essere inizializzati al momento della dichiarazione e non possono essere null
.
Esempio di dichiarazione di un riferimento:
int b = 10;
int &ref = b; // ref è un riferimento a b
Regole per l’uso dei Valori per Riferimento
- Inizializzazione obbligatoria: I riferimenti devono essere inizializzati quando vengono dichiarati.
- Alias permanente: Una volta che un riferimento è inizializzato a una variabile, non può essere cambiato per riferirsi a un’altra variabile.
- Non
null
: A differenza dei puntatori, i riferimenti non possono essere nulli. - Uso con funzioni: I riferimenti sono spesso usati per passare argomenti a funzioni senza fare una copia dell’argomento.
Esempi
Passaggio di Argomenti a Funzioni
I riferimenti permettono di passare variabili a funzioni senza copiarle, il che può migliorare le prestazioni.
void increment(int &ref) {
ref++;
}
int main() {
int a = 10;
increment(a);
printf("%d\\n", a); // Output: 11
return 0;
}
Restituzione di Valori da Funzioni
Le funzioni possono restituire riferimenti, permettendo di utilizzare l’oggetto restituito come l-value.
Always show details
int& getElement(int *arr, int index) {
return arr[index];
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
getElement(arr, 1) = 10;
printf("%d\\n", arr[1]); // Output: 10
return 0;
}
Overloading degli Operatori
I riferimenti sono spesso utilizzati nelle funzioni di overloading degli operatori per evitare la copia di oggetti.
Always show details
class Number {
private:
int value;
public:
Number(int v) : value(v) {}
Number& operator++() {
++value;
return *this;
}
int getValue() const { return value; }
};
int main() {
Number num(10);
++num;
printf("%d\\n", num.getValue()); // Output: 11
return 0;
}
Aritmetica dei Puntatori
L’aritmetica dei puntatori consente di eseguire operazioni matematiche sui puntatori. Le operazioni comuni includono l’incremento (++), il decremento (–), l’addizione (+) e la sottrazione (-). Queste operazioni sono utili quando si lavora con array e altre strutture di dati.
Incremento e Decremento
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
p++; // punta al secondo elemento dell'array
printf("%d\\n", *p); // Output: 2
p--; // torna a puntare al primo elemento dell'array
printf("%d\\n", *p); // Output: 1
Addizione e Sottrazione
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
p = p + 2; // punta al terzo elemento dell'array
printf("%d\\n", *p); // Output: 3
p = p - 1; // punta al secondo elemento dell'array
printf("%d\\n", *p); // Output: 2
Differenza tra Puntatori
La differenza tra due puntatori della stessa base di tipo restituisce il numero di elementi tra di loro.
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[1];
int *p2 = &arr[4];
int diff = p2 - p1; // diff sarà uguale a 3
printf("%d\\n", diff); // Output: 3
Casi Particolari
Puntatori void
Il tipo di puntatore void
è un tipo speciale di puntatore. In C++, void rappresenta l’assenza di tipo.
Ciò conferisce ai puntatori void una grande flessibilità, potendo puntare a qualsiasi tipo di dati. Per controparte hanno una grande limitazione: i dati a cui puntano non possono essere dereferenziati direttamente (il che è logico, poiché non abbiamo alcun tipo a cui dereferenziare) e per questo motivo, qualsiasi indirizzo in un puntatore void deve essere trasformato in un altro tipo di puntatore che punti a un tipo di dati concreto prima di essere dereferenziato.
Uno dei suoi possibili usi potrebbe essere quello di passare parametri generici a una funzione. Ad esempio:
#include <iostream>
using namespace std;
void increase (void* data, int psize)
{
if ( psize == sizeof(char) )
{ char* pchar; pchar=(char*)data; ++(*pchar); }
else if (psize == sizeof(int) )
{ int* pint; pint=(int*)data; ++(*pint); }
}
int main ()
{
char a = 'x';
int b = 1602;
increase (&a,sizeof(a));
increase (&b,sizeof(b));
cout << a << ", " << b << '\n';
return 0;
}
sizeof
è un operatore integrato nel linguaggio C++ che restituisce la dimensione in byte del suo argomento.
Per i tipi di dati non dinamici, questo valore è una costante. Pertanto, ad esempio, sizeof(char) è 1, perché char ha sempre una dimensione di un byte.
Puntatori const
I puntatori possono essere utilizzati per accedere a una variabile tramite il suo indirizzo e questo accesso può includere la modifica del valore puntato.
Ma è anche possibile dichiarare puntatori che possono accedere al valore puntato per leggerlo, ma non per modificarlo:
int x;
int y = 10;
const int * p = &y;
x = *p; // ok: lettura di p
*p = x; // errore: modifica di p, che è qualificato come const
NB: un puntatore a non-const può essere implicitamente convertito in un puntatore a const; Ma non viceversa!
Uno dei casi d’uso dei puntatori a elementi const è come parametri di funzione:
// puntatori come argomenti
#include <iostream>
using namespace std;
void increment_all (int* start, int* stop)
{
int * current = start;
while (current != stop)
{
++(*current); // incrementa il valore puntato
++current; // incrementa il puntatore
}
}
void print_all (const int* start, const int* stop)
{
const int * current = start;
while (current != stop)
{
cout << *current << '\n';
++current; // incrementa il puntatore
}
}
int main ()
{
int numbers[] = {10,20,30};
increment_all (numbers,numbers+3);
print_all (numbers,numbers+3);
return 0;
}
I puntatori possono anche essere essi stessi const. E questo viene specificato aggiungendo const al tipo puntato (dopo l’asterisco):
int x;
int * p1 = &x; // puntatore non const a int non const
const int * p2 = &x; // puntatore non costante a const int
int const * p2 = &x; // puntatore non costante a const int
int * const p3 = &x; // puntatore costante a non costante int
const int * const p4 = &x; // puntatore costante a const int
Puntatori non validi e puntatori null
I puntatori possono puntare a qualsiasi indirizzo, inclusi gli indirizzi che non fanno riferimento a nessun elemento valido. Esempi tipici di ciò sono i puntatori non inizializzati e i puntatori a elementi inesistenti di un array:
int * p; // puntatore non inizializzato (variabile locale)
int myarray[10];
int * q = myarray+20; // elemento fuori dai limiti
In C++, i puntatori possono assumere qualsiasi valore di indirizzo, indipendentemente dal fatto che ci sia effettivamente qualcosa a quell’indirizzo o meno. Ciò che può causare un errore è la dereferenziazione di tale puntatore (ovvero, l’accesso effettivo al valore a cui puntano).
Quando è necessario inizializzare un puntatore a null si può usare la seguente sintassi:
int * p = 0;
int * q = nullptr;
int * r = NULL; // vecchia sintassi alisa per nullptr
Qui, sia p che q sono puntatori nulli, il che significa che puntano esplicitamente a nessun indirizzo di memoria.
Riferimenti
Quest'opera è distribuita con Licenza Creative Commons Attribuzione - Condividi allo stesso modo 4.0 Internazionale Theme Moonwalk