Home

ITA - Appunti Cpp 012 - Puntatori

linux cpp

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 :

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

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