Διαδρομές
/
C++
C++
/
Ύλη
/
Δείκτες
Δε

Δείκτες σε C++

1 άσκηση

Σχετικά με την έννοια Δείκτες

Όπως πολλές άλλες γλώσσες, η C++ έχει δείκτες. Ήδη γνωρίζεις ότι οι αναφορές και οι δείκτες είναι παρόμοιοι, αλλά σκέψου τους ως ένα επίπεδο πιο κοντά στην εσωτερική λειτουργία του υπολογιστή σου. Οι δείκτες είναι μεταβλητές που περιέχουν διευθύνσεις αντικειμένων. Χρησιμοποιούνται για να αλληλεπιδράς άμεσα με αντικείμενα, επιτρέποντας τη δυναμική δέσμευση μνήμης και τον αποδοτικό χειρισμό δεδομένων στη C++.

Αν έρχεσαι πρώτη φορά σε επαφή με τους δείκτες, μπορεί να σου φανούν λίγο μυστηριώδεις, αλλά μόλις τους συνηθίσεις, είναι αρκετά απλοί.

Αποτελούν κρίσιμο κομμάτι της C++, οπότε αφιέρωσε λίγο χρόνο για να τους κατανοήσεις πραγματικά. Η στοιχειώδης εκδοχή τους σε αυτή την ενότητα ονομάζεται επίσης χαζός δείκτης ή ακατέργαστος δείκτης. Με τη σύγχρονη C++ υπάρχουν και οι έξυπνοι δείκτες, αλλά ο βασικός τύπος δεν είναι καθόλου έξυπνος και πρέπει να χειριστείς όλη τη δουλειά με το χέρι.

Πριν εμβαθύνεις στις λεπτομέρειες, αξίζει να κατανοήσεις τη χρήση των δεικτών. Οι δείκτες είναι ένας τρόπος να μοιράζεσαι τη διεύθυνση ενός αντικειμένου με άλλα μέρη του προγράμματός μας, κάτι χρήσιμο για δύο βασικούς λόγους:

  1. Όπως οι αναφορές, οι δείκτες αποφεύγουν τα αντίγραφα και βοηθούν στη μείωση του αποτυπώματος πόρων του προγράμματός σου.
  2. Σε αντίθεση με τις αναφορές, οι δείκτες μπορούν να ανατεθούν εκ νέου σε διαφορετικά αντικείμενα.
  3. Οι δείκτες μπορούν επίσης να δείχνουν σε μια μηδενική τιμή, για να δηλώσουν ότι προς το παρόν δεν δείχνουν σε κανένα αντικείμενο.

Γενική σύνταξη

Η δήλωση ενός δείκτη στη C++ περιλαμβάνει τον προσδιορισμό του τύπου δεδομένων στον οποίο δείχνει ο δείκτης, ακολουθούμενο από έναν αστερίσκο (*) και το όνομα του δείκτη. Όταν δηλώνονται δείκτες, δεν αρχικοποιούνται αυτόματα. Χωρίς ρητή ανάθεση, ένας δείκτης συνήθως περιέχει μια απροσδιόριστη τιμή, που συχνά αναφέρεται ως "διεύθυνση σκουπιδιών". Αν και ορισμένοι μεταγλωττιστές μπορεί να αρχικοποιούν τους δείκτες σε nullptr, αυτή η συμπεριφορά δεν είναι εγγυημένη σε όλους τους μεταγλωττιστές, οπότε είναι απαραίτητο να μη βασίζεσαι σε αυτή. Είναι βέλτιστη πρακτική να αρχικοποιείς ρητά τους ακατέργαστους δείκτες και να επαληθεύεις ότι δεν είναι μηδενικοί πριν τους χρησιμοποιήσεις, ώστε να αποφύγεις πιθανά προβλήματα.

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

Για να αναθέσεις τη διεύθυνση μιας μεταβλητής σε έναν δείκτη, χρησιμοποιείς τον τελεστή διεύθυνσης (&). Η αποαναφορά ενός δείκτη γίνεται με τον τελεστή έμμεσης προσπέλασης (*).

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

Προσοχή: η αποαναφορά πρέπει να γίνεται ρητά, ενώ οι αναφορές λειτουργούσαν απλώς ως ψευδώνυμο.

Αριθμητική δεικτών

Η αριθμητική δεικτών σου επιτρέπει να εκτελείς αριθμητικές πράξεις σε δείκτες, κάτι που είναι ιδιαίτερα χρήσιμο όταν εργάζεσαι με πίνακες. Η πρόσθεση ενός ακέραιου σε έναν δείκτη τον κάνει να δείχνει σε διαφορετικό στοιχείο.

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

Η αριθμητική δεικτών στη C++ μπορεί εύκολα να οδηγήσει σε μη καθορισμένη συμπεριφορά αν δεν τη χειριστείς προσεκτικά. Η μη καθορισμένη συμπεριφορά μπορεί να εκδηλωθεί με απροσδόκητα αποτελέσματα του προγράμματος, καταρρεύσεις ή ακόμη και ευπάθειες ασφαλείας. Ένα διαβόητο παράδειγμα των συνεπειών της μη καθορισμένης συμπεριφοράς συνέβη στην έκρηξη του πυραύλου Ariane 5 το 1996, όπου μια εξαίρεση λογισμικού που προκλήθηκε από τη μετατροπή ενός αριθμού κινητής υποδιαστολής 64 bit σε έναν προσημασμένο ακέραιο 16 bit οδήγησε σε καταστροφική αποτυχία.

Πρόσβαση σε μεταβλητές μέλους

Στη C++, ο τελεστής -> χρησιμοποιείται για την πρόσβαση σε μέλη ενός αντικειμένου μέσω ενός δείκτη προς αυτό το αντικείμενο. Είναι μια συντομογραφία που απλοποιεί την πρόσβαση σε μέλη αντικειμένων στα οποία δείχνουν δείκτες. Για παράδειγμα, αν ο ptr είναι δείκτης σε ένα αντικείμενο με μεταβλητή μέλους x, αντί να χρησιμοποιήσεις το (*ptr).x, μπορείς να χρησιμοποιήσεις απευθείας το ptr->x. Αυτός ο τελεστής βελτιώνει την αναγνωσιμότητα του κώδικα και μειώνει τη φλυαρία όταν εργάζεσαι με δείκτες σε αντικείμενα.

Ακολουθεί ένα σύντομο παράδειγμα, με μια δομή Superhero που έχει μια μεταβλητή μέλους superpower. Η κύρια συνάρτηση δημιουργεί έναν δείκτη dianaPrince σε ένα αντικείμενο Superhero (που αντιπροσωπεύει τη Wonder Woman). Ο τελεστής -> χρησιμοποιείται για την πρόσβαση στη μεταβλητή μέλους superpower, παρουσιάζοντας το εμβληματικό "Lasso of Truth" της Wonder Woman.

struct Superhero {
    std::string superpower;
};

Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";

// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

// Memory cleanup:
delete dianaPrince;

Δείκτες έναντι αναφορών

Οι δείκτες και οι αναφορές επιτρέπουν και οι δύο έμμεση πρόσβαση σε αντικείμενα, αλλά διαφέρουν ως προς τις δυνατότητες και την ασφάλεια που προσφέρουν. Οι δείκτες προσφέρουν την ευελιξία να αλλάζεις το αντικείμενο στο οποίο δείχνουν και μπορούν να οριστούν σε μηδενική τιμή. Ωστόσο, αυτή η ευελιξία εισάγει κινδύνους, όπως η αποαναφορά μηδενικών δεικτών ή η δημιουργία εκκρεμών δεικτών. Οι αναφορές, από την άλλη, δεν μπορούν να είναι μηδενικές και δεσμεύονται σε έγκυρα αντικείμενα κατά τη δημιουργία τους, αποφεύγοντας αυτούς τους κινδύνους. Δεδομένης της ασφαλέστερης φύσης τους, οι αναφορές θα πρέπει να προτιμώνται έναντι των δεικτών, εκτός αν οι επιπλέον δυνατότητες που παρέχουν οι δείκτες είναι απαραίτητες.

Advanced

Δυναμική δέσμευση μνήμης και μηδενικοί δείκτες

Στη C++, το new χρησιμοποιείται για τη δυναμική δέσμευση μνήμης για αντικείμενα στον σωρό κατά τη διάρκεια εκτέλεσης. Επιστρέφει έναν δείκτη στη δεσμευμένη διεύθυνση μνήμης, επιτρέποντας σε αντικείμενα να έχουν διάρκεια ζωής που δεν συνδέεται με κάποιο συγκεκριμένο πεδίο εμβέλειας. Ωστόσο, είναι σημαντικό να συνδυάζεις πάντα το new με το delete για να απελευθερώνεις σωστά τη μνήμη. Η δυναμική δέσμευση μνήμης πρέπει να χρησιμοποιείται με προσοχή, για να αποφεύγονται κοινά προβλήματα όπως οι διαρροές μνήμης και η πρόσβαση σε μνήμη που έχει ήδη απελευθερωθεί.

Οι δείκτες μπορούν επίσης να οριστούν σε μια ειδική τιμή που ονομάζεται μηδενικός δείκτης, η οποία δηλώνει ότι δεν δείχνουν σε έγκυρη διεύθυνση. Οι μηδενικοί δείκτες χρησιμοποιούνται συνήθως για την αρχικοποίηση δεικτών πριν τους αναθέσεις σε έγκυρες τιμές.

// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;

Ένας δείκτης θα πρέπει είτε να περιέχει τη διεύθυνση ενός έγκυρου αντικειμένου, είτε να έχει οριστεί σε nullptr. Έτσι χρειάζεται μόνο να ελέγχεις τους δείκτες για μηδενική τιμή και μπορείς να υποθέσεις ότι κάθε μη μηδενικός δείκτης είναι έγκυρος. Όταν ένα αντικείμενο καταστρέφεται, τυχόν δείκτες προς το κατεστραμμένο αντικείμενο παραμένουν εκκρεμείς (δεν ορίζονται αυτόματα σε nullptr). Είναι δική σου ευθύνη να εντοπίζεις αυτές τις περιπτώσεις και να φροντίζεις ώστε αυτοί οι δείκτες να ορίζονται στη συνέχεια σε nullptr.

Σε παλαιότερο κώδικα, μπορεί να συναντήσεις δύο εναλλακτικές του nullptr. Πρώτον, το literal 0 ερμηνεύεται ειδικά ως μηδενική τιμή για δείκτες, αν και είναι η μόνη περίπτωση όπου ένα ακέραιο literal μπορεί να ανατεθεί σε δείκτη. Δεύτερον, η preprocessor macro NULL, κληρονομημένη από τη C και ορισμένη στην κεφαλίδα <cstddef>, είναι μια ακόμη αναπαράσταση του μηδενικού δείκτη, αν και η χρήση της είναι λιγότερο συχνή στον σύγχρονο κώδικα C++.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα

Μάθε την έννοια Δείκτες

Η εξάσκηση είναι κλειδωμένη

Ξεκλείδωσε 1 ακόμη άσκηση για να εξασκηθείς στην έννοια Δείκτες