I Puntatori in C++

Scritto da ambrix on . Postato in Corsi Programmazione

logo programmazione c++

Lezione#07 Benvenuti alla settima lezione del corso C++ in 11 Passi, in questa tappa introdurremo i Puntatori.

Il C++ prevede puntatori a funzioni e puntatori a dati di qualsiasi natura, semplici o strutturati. In generale, tra gli ambiti di utilizzo, i puntatori risultano uno strumento potente per l’elaborazione di stringhe (array) e per il riferimento a locazioni specifiche della memoria. In quest’ultimo caso si può avere riferimento a locazioni associate a variabili temporanee così come a dispositivi hardware, ad es. Input/Output. In questa lezione tratteremo i puntatori a dati.

Nel linguaggio C++, un programma avrà la memoria suddivisa in due parti: lo stack, dove le variabili sono memorizzate e l’heap, una parte di memoria non utilizzata ma disponibile ad accogliere dati durante l’esecuzione del programma.

Proviamo ad eseguire due volte consecutive il programma che segue:

#include <iostream>
using namespace std;

int main(){
    string str = "Targeting";
    int num = 16;
    cout << &str << " and " << &num;
}

0x7ffc1585f530 and 0x7ffc1585f52c 
0x7ffc5af663b0 and 0x7ffc5af663ac

Risulta ben evidente che ad ogni esecuzione del listato, viene allocata una cella di memoria differente. Ciò avviene sia per la variabile str, sia per la variabile num. Pertanto l’allocazione di memoria sarà differente ad ogni nuovo run del programma in atto.  Il simbolo & (e commerciale), se inserito prima di una variabile, restituisce l’indirizzo di memoria della variabile stessa. Questo operatore è chiamato address-of operator ma è conosciuto anche come operatore unario di referenziazione.

Un altro operatore, complementare all’address-of operator e dalle notevoli potenzialità, è il cosiddetto dereference operator. Questo operatore si indica con il simbolo * (asterisco o star) e, se applicato ad una variabile puntatore, restituisce il valore contenuto nella locazione di memoria puntata.

#include <iostream>
using namespace std;

int main(){
    int num = 16;
    cout << *&num;
}

16

Ok! Evidente che il dereference operator è “la funzione inversa” dell’address-of operator. In altre parole è come se questi operatori si elidessero a vicenda. Con *&num otteniamo in output il valore della variabile num.

Un puntatore pertanto è una variabile che memorizza una locazione di memoria così come int memorizza un intero e string memorizza sequenze di caratteri. Vediamo il seguente esempio:

#include <iostream>
using namespace std;

int main(){
    double speed = 45.8;
    double *spdPtr = &speed;
    cout << "spdPtr: " << spdPtr << endl;
    cout << "*spdPtr: " << *spdPtr << endl;
}

spdPtr: 0x7ffc6758ec08
*spdPtr: 240.5

Importante dichiarare il puntatore dello stesso tipo della variabile a cui vuole puntare.

Possiamo inoltre fare in modo che il puntatore non memorizzi alcun indirizzo di memoria. Vediamo un esempio:

#include <iostream>
using namespace std;

int main(){
    int *ptr = 0;
    int *nullPtr = NULL;
    cout << "ptr =  " << ptr << endl;
    cout << "*nullPtr = " << nullPtr << endl;
}

ptr = 0
nullPtr = 0

In entrambi i casi abbiamo 0 come risultato. Infatti, per avere un cosiddetto NULL Pointer possiamo assegnare al puntatore il valore 0 oppure il valore NULL. Un modo semplice e veloce per verificare se un puntatore ha un valore NULL è usare le istruzioni condizionali:

#include 
using namespace std;

int main(){
    int *nullPtr = NULL;
    if (nullPtr == 0)
        cout << "Questo è un NULL-Pointer" << endl;
    else
        cout << &nullPtr;
}

Questo è un NULL-Pointer

Ben fatto! Come già scritto, questo è un modo per avere sotto controllo il flusso di esecuzione del codice quando si usano i puntatori. Ma vediamo qualcosa di più operativo. Possiamo usare i puntatori per scorrere un array:

#include 
using namespace std;

int main(){
    int ptrArr[3] = {0, 1, 2};
    int *ptr;
    ptr = ptrArr;
    int ii = 0;
    int lenArr = sizeof(ptrArr)/sizeof(ptrArr[0]);

    while (ii<lenArr){
        cout << *ptr << endl;
        ptr++;
        ii++;
    }

}

0
1
2

Wow! Il puntatore scorre tutto l’array, puntando ogni elemento al suo interno. In questo modo possiamo stampare l’array spostando semplicemente il nostro puntatore sugli indici desiderati.

Il ciclo while ci permette di ripetere più volte un blocco di istruzioni, fintantoché una condizione è vera. Affronteremo il ciclo while nella prossima lezione!

Facciamo quindi qualche considerazione finale sulle locazioni di memoria nel caso in cui dichiariamo e inizializziamo un array:

#include 
using namespace std;

int main(){
    int ptrArr[3] = {0, 1, 2};
    int *ptr;
    ptr = ptrArr;
    int ii = 0;
    int lenArr = sizeof(ptrArr)/sizeof(ptrArr[0]);

    while (ii<lenArr){
        cout << *ptr << " at address: " << ptr << endl;
        ptr++;
        ii++;
    }

    cout << ptrArr << endl;
    cout << &ptrArr[0];

}

0 at address: 0x7ffe73886900
1 at address: 0x7ffe73886904
2 at address: 0x7ffe73886908
0x7ffe73886900
0x7ffe73886900

Da una analisi attenta dell’output si evince che ogni elemento dell’array ha un indirizzo diverso. Inoltre, provando a stampare l’array otteniamo l’indirizzo di memoria del solo primo elemento. Quindi array e &array[0] hanno lo stesso indirizzo –> array e puntatori sono molto simili!

Ritorna alla precedente <– []–> Vai alla successiva

Per Richieste, Avvisi e Lasciti Ereditari

Disclaimer

I contenuti di questo Blog (testi, immagini, foto, etc.) sono di mia creazione, tranne nei casi dove espressamente indicato.