Η χρήση των δεικτών ή pointers στη γλώσσα Cpp αποτελεί ένα πανίσχυρο εργαλείο για τον προγραμματιστή. Παρέχει διαφάνεια στην τοποθεσία αποθήκευσης των μεταβλητών και των αντικειμένων στη μνήμη του υπολογιστή και επιτρέπει τις απευθείας αναθέσεις τιμών σε αυτές.
Η χρήση των δεικτών ή pointers στη γλώσσα Cpp: αναπαράσταση μεταβλητών στη μνήμη
Οι μεταβλητές αποτελούν ουσιαστικά τοποθεσίες στη μνήμη του υπολογιστή οι οποίες μπορούν να προσπελαστούν από τον compiler απλώς με τη σύνταξη του κάθε identifier, δηλαδή του ονόματός τους. Με αυτόν τον τρόπο, το πρόγραμμα δε χρειάζεται να ενημερώνεται για τη φυσική τοποθεσία των δεδομένων στη μνήμη.
Για ένα πρόγραμμα ανεπτυγμένο σε C++, η μνήμη ενός υπολογιστή αποτελεί μία ακολουθία κελιών που μετρώνται σε byte, καθένα από τα οποία καθορίζεται από τη δική του μοναδική διεύθυνση μνήμης. Τα κελιά αυτά είναι ταξινομημένα κατά τρόπο τέτοιο ώστε να επιτρέπεται η αποθήκευση δεδομένων μεγαλύτερων του ενός byte σε κελιά μνήμης διαδοχικών διευθύνσεων. Για παράδειγμα το κελί με διεύθυνση 2745 έπεται του 2744 και προηγείται του 2746. Κάθε κελί μπορεί να εντοπιστεί μέσω της μοναδικής του διεύθυνσης και κάθε μεταβλητή μπορεί να προσπελαστεί αρκεί να είναι γνωστό το πρώτο κελί της αντίστοιχης δεσμευμένης μνήμης.
Για την υλοποίηση της πρακτικής αυτής, δημιουργούμε αντικείμενα τύπου pointer, τα οποία αποθηκεύουν τη διεύθυνση του πρώτου εκ των διαδοχικών κελιών που δεσμεύονται από μία μεταβλητή. Κατά τη δήλωση μίας μεταβλητής, δεσμεύεται η μνήμη που απαιτείται για την αποθήκευσή της και σε αυτήν ανατίθεται μία διεύθυνση μνήμης.
Γενικότερα τα προγράμματα σε C++ δεν επιλέγουν καθεαυτά την ακριβή διεύθυνση, αλλά η διαδικασία αυτή πραγματοποιείται από το περιβάλλον εκτέλεσης του προγράμματος. Το λειτουργικό σύστημα αναλαμβάνει το ρόλο ανάθεσης διευθύνσεων. Για αυτό και κρίνεται χρήσιμη η δυνατότητα ενός προγράμματος να ανακτά την τοποθεσία μίας οποιασδήποτε μεταβλητής κατά την εκτέλεση του προγράμματος (runtime), προκειμένου να προσπελάσει τα συσχετιζόμενα κελιά μνήμης.
Η χρήση των δεικτών ή pointers στη γλώσσα Cpp: reference operator (&)
Η διεύθυνση μίας μεταβλητής μπορεί να ανακτηθεί με τη σύνταξη του συμβόλου & πριν από το όνομά της. Η σύνταξη για τη σύνδεση ενός δείκτη με μία μεταβλητή ακεραίου τύπου με όνομα myIntNumber είναι της εξής μορφής:
myPointer = &myIntNumber;
Η εντολή αυτή εκχωρεί τη διεύθυνση μνήμης της μεταβλητής myIntNumber στο δείκτη με όνομα myPointer. Το σύμβολο ονομάζεται τελεστής address-of και μεταφράζεται ως “η διεύθυνση μνήμης” της μεταβλητής. Χρησιμοποιείται για την εκχώρηση τιμής απευθείας σε συγκεκριμένη διεύθυνση μνήμης και όχι στην ίδια τη μεταβλητή.
Η ακριβής διεύθυνση δεν είναι δυνατό να είναι γνωστή πριν την εκτέλεση του προγράμματος (runtime), οπότε ας υποθέσουμε ότι η μεταβλητή myIntNumber βρίσκεται στη διεύθυνση 2745. Τότε, η κατάσταση στην μνήμη, μετά την εκτέλεση του προηγούμενου κώδικα, θα μπορούσε να είναι κάπως έτσι:
Έστω ότι έχουμε δηλώσει δύο μεταβλητές ακεραίου τύπου με ονόματα myIntNumber και mySecondIntNumber, καθώς και ένα δείκτη με όνομα myPointer. Ας μελετήσουμε τις ακόλουθες γραμμές κώδικα και τις εκχωρήσεις που πραγματοποιούνται:
myIntNumber = 25; //1 myPointer = &myIntNumber; //2 mySecondIntNumber = myIntNumber + 5; //3
- 1: Εκχωρείται η τιμή 25 στη μεταβλητή με όνομα myIntNumber.
- 2: Εκχωρείται η διεύθυνση μνήμης της μεταβλητής myIntNumber στο δείκτη με όνομα myPointer (εδώ 2745).
- 3: Εκχωρείται το περιεχόμενο της μεταβλητής myIntNumber στο οποίο προσθέτουμε 5, (δηλαδή θα έχει την τιμή 30), στη μεταβλητή με όνομα mySecondIntNumber.
Η συντακτική διαφορά μεταξύ των γραμμών 2 και 3 είναι η χρήση του τελεστή address-of (σύμβολο &). Η γραμμή 2 επιδεικνύει τη χρήση ενός δείκτη στη C++ (εδώ myPointer) για την αποθήκευση της διεύθυνσης μνήμης μίας μεταβλητής. Οι δείκτες αποτελούν ένα πολύ ισχυρό εργαλείο για προγραμματισμό χαμηλού επιπέδου ( lower-level).
Η χρήση των δεικτών ή pointers στη γλώσσα Cpp: dereference operator (*)
Ένα ιδιαίτερο χαρακτηριστικό των δεικτών είναι ότι μπορούν να χρησιμοποιηθούν για την προσπέλαση της ίδιας της μεταβλητής με την οποία έχουν συσχετιστεί. Για την αξιοποίηση αυτού του χαρακτηριστικού, απαιτείται η σύνταξη του τελεστή dereference (σύμβολο *) ως prefix στο όνομα του δείκτη. Ο τελεστής αυτός μεταφράζεται ως “η τιμή της μεταβλητής στην οποία δείχνει” ο δείκτης. Σύμφωνα με το προηγούμενο παράδειγμα, η εντολή εκχώρησης
mySecondIntNumber = *myPointer ;
εκχωρεί στη μεταβλητή mySecondIntNumber την τιμή της μεταβλητής στην οποία δείχνει ο pointer με όνομα myPointer (εδώ την τιμή 25). Ουσιαστικά ο δείκτης myPointer περιέχει την τιμή 2745, ενώ το περιεχόμενο που δείχνει η διεύθυνση, δηλαδή *myPointer περιέχει την τιμή 25.
[ms_alert icon=”fa-star” box_shadow=”no” dismissable=”no” class=”” id=””]Συνοπτικά: η διεύθυνση μνήμης ανακτάται με το σύμβολο & και το όνομα της μεταβλητής, ενώ η τιμή της μεταβλητής ανακτάται με το σύμβολο * και το όνομα του δείκτη.[/ms_alert]
Λόγω της ικανότητας του δείκτη να αναφέρεται απευθείας στην τιμή κάποιας μεταβλητής, κάθε δείκτης έχει διαφορετικές ιδιότητες ανάλογα με το αν δείχνει σε τιμή τύπου char ή τιμή τύπου int ή float. Κατά τη διαδικασία dereference, ο τύπος πρέπει να είναι γνωστός. Γι’ αυτό το λόγο και χρειάζεται η σύνταξη του εκάστοτε τύπου δεδομένων στη δήλωσή του.