Όπως και πολλές άλλες γλώσσες, η C++ έχει δείκτες. Ήδη ξέρεις ότι οι αναφορές και οι δείκτες μοιάζουν, αλλά σκέψου τους ως ένα επίπεδο πιο κοντά στο πώς λειτουργεί εσωτερικά ο υπολογιστής σου. Οι δείκτες είναι μεταβλητές που κρατούν διευθύνσεις αντικειμένων. Χρησιμοποιούνται για να αλληλεπιδράς άμεσα με αντικείμενα, επιτρέποντας δυναμική δέσμευση μνήμης και αποδοτικό χειρισμό δεδομένων στη C++.
Αν τώρα αρχίζεις με τους δείκτες, μπορεί να σου φανούν λίγο μυστηριώδεις, αλλά μόλις τους συνηθίσεις, είναι αρκετά απλοί.
Αποτελούν κρίσιμο κομμάτι της C++, οπότε αφιέρωσε λίγο χρόνο για να τους καταλάβεις πραγματικά. Η πιο απλή εκδοχή που θα δεις σε αυτή την ενότητα ονομάζεται επίσης dumb pointer ή raw pointer. Στη σύγχρονη C++ υπάρχουν και οι smart pointers, αλλά ο βασικός τύπος δεν είναι καθόλου έξυπνος και πρέπει να κάνεις όλη τη δουλειά με το χέρι.
Πριν μπούμε στις λεπτομέρειες, αξίζει να καταλάβεις τη χρήση των δεικτών. Οι δείκτες είναι ένας τρόπος να μοιράζεσαι τη διεύθυνση ενός αντικειμένου με άλλα μέρη του προγράμματός μας, κάτι που είναι χρήσιμο για δύο βασικούς λόγους:
Η δήλωση ενός δείκτη στη C++ περιλαμβάνει τον καθορισμό του τύπου δεδομένων στον οποίο δείχνει ο δείκτης, ακολουθούμενο από έναν αστερίσκο (*) και το όνομα του δείκτη.
Όταν δηλώνονται δείκτες, δεν αρχικοποιούνται αυτόματα.
Χωρίς ρητή ανάθεση, ένας δείκτης συνήθως κρατά μια απροσδιόριστη τιμή, που συχνά αναφέρεται ως "διεύθυνση σκουπιδιών".
Αν και ορισμένοι μεταγλωττιστές μπορεί να αρχικοποιούν τους δείκτες σε nullptr, αυτή η συμπεριφορά δεν είναι εγγυημένη σε όλους τους μεταγλωττιστές, οπότε είναι σημαντικό να μην βασίζεσαι σε αυτή.
Είναι καλή πρακτική να αρχικοποιείς ρητά τους raw pointers και να ελέγχεις ότι δεν είναι null πριν τους χρησιμοποιήσεις, ώστε να αποφύγεις πιθανά προβλήματα.
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);
Η αριθμητική δεικτών στη C++ μπορεί εύκολα να οδηγήσει σε απροσδιόριστη συμπεριφορά αν δεν τη χειριστείς προσεκτικά. Η απροσδιόριστη συμπεριφορά μπορεί να εκδηλωθεί ως απροσδόκητα αποτελέσματα του προγράμματος, καταρρεύσεις ή ακόμα και κενά ασφαλείας. Ένα διαβόητο παράδειγμα των συνεπειών της απροσδιόριστης συμπεριφοράς συνέβη με την έκρηξη του πυραύλου Ariane 5 το 1996, όπου μια εξαίρεση λογισμικού, που προκλήθηκε από τη μετατροπή ενός αριθμού κινητής υποδιαστολής 64 bit σε έναν προσημασμένο ακέραιο 16 bit, οδήγησε σε καταστροφική αποτυχία.
Στη C++, ο τελεστής -> χρησιμοποιείται για να έχεις πρόσβαση στα μέλη ενός αντικειμένου μέσω ενός δείκτη σε αυτό το αντικείμενο.
Είναι μια συντομογραφία που απλοποιεί την πρόσβαση στα μέλη αντικειμένων στα οποία δείχνουν δείκτες.
Για παράδειγμα, αν ο ptr είναι δείκτης σε ένα αντικείμενο με μεταβλητή μέλους x, αντί να χρησιμοποιήσεις (*ptr).x, μπορείς να χρησιμοποιήσεις απευθείας ptr->x.
Αυτός ο τελεστής βελτιώνει την αναγνωσιμότητα του κώδικα και μειώνει τη φλυαρία όταν δουλεύεις με δείκτες σε αντικείμενα.
Ορίστε ένα σύντομο παράδειγμα, με μια struct Superhero που έχει μεταβλητή μέλους superpower.
Η συνάρτηση main δημιουργεί έναν δείκτη 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;
Οι δείκτες και οι αναφορές επιτρέπουν και οι δύο έμμεση πρόσβαση σε αντικείμενα, αλλά διαφέρουν ως προς τις δυνατότητες και τα ζητήματα ασφάλειας. Οι δείκτες προσφέρουν την ευελιξία να αλλάζουν το αντικείμενο-στόχο τους και μπορούν να οριστούν σε null. Ωστόσο, αυτή η ευελιξία εισάγει κινδύνους, όπως η αποαναφορά μηδενικών δεικτών ή η δημιουργία εκκρεμών δεικτών. Οι αναφορές, από την άλλη, δεν μπορούν να είναι null και δεσμεύονται σε έγκυρα αντικείμενα κατά τη δημιουργία τους, αποφεύγοντας αυτούς τους κινδύνους. Δεδομένης της ασφαλέστερης φύσης τους, οι αναφορές προτιμώνται έναντι των δεικτών, εκτός αν οι επιπλέον λειτουργίες που παρέχουν οι δείκτες είναι απαραίτητες.
Καλώς όρισες, Μηχανικέ! Είσαι ένας από τους τελευταίους βετεράνους του Ιδρύματος Speedywagon, μιας μυστικής οργάνωσης που επί δεκαετίες μάχεται εναντίον αρχαίων απειλών όπως οι Pillar Men. Στο πλαίσιο αυτής της προσπάθειας, έχεις περάσει χρόνια συντηρώντας τα τεχνολογικά συστήματα του Ιδρύματος, που είναι χτισμένα με έναν συνδυασμό πρωτοποριακής τεχνολογίας και παλιών βιβλιοθηκών.
Τελευταία, όμως, οι αισθητήρες που παρακολουθούν τη δραστηριότητα των Pillar Men δυσλειτουργούν. Τα συστήματα του Ιδρύματος έχουν παλιώσει, και ο κώδικας αλληλεπιδρά με μια βιβλιοθήκη C++ από τα παλιά που δεν μπορεί να ενημερωθεί. Η αποστολή σου είναι να υλοποιήσεις τέσσερις βασικές συναρτήσεις που παρακολουθούν τη δραστηριότητα των αισθητήρων Pillar Men χρησιμοποιώντας μια παλιομοδίτικη βιβλιοθήκη βασισμένη σε δείκτες.
Ως σύγχρονος μηχανικός C++, θα προτιμούσες να χρησιμοποιήσεις έξυπνους δείκτες, αλλά δυστυχώς ο παλιός κώδικας απαιτεί σεβασμό στους παλιούς τρόπους. Η μοίρα της ανθρωπότητας μπορεί να κρέμεται από αυτούς τους δείκτες, οπότε προχώρα προσεκτικά, και είθε η ενέργεια Hamon να σε οδηγήσει.
Καθώς οι μετρήσεις των αισθητήρων μπορούν να είναι τεράστιες, σου παρέχουμε μια δοκιμαστική struct που χρησιμοποιείται στην πραγματική βιβλιοθήκη. Ο κώδικας έχει ήδη υλοποιηθεί για σένα στο αρχείο κεφαλίδας.
struct pillar_men_sensor {
int activity{};
std::string location{};
std::vector<int> data{};
};
connection_check)Η πρώτη σου αποστολή είναι να βεβαιωθείς ότι ο αισθητήρας Pillar Men είναι σωστά συνδεδεμένος.
Δεν μπορούμε να έχουμε ψευδείς συναγερμούς που προκαλούνται από αποσυνδεδεμένους αισθητήρες.
Θα γράψεις μια συνάρτηση connection_check, η οποία ελέγχει αν ο δείκτης του αισθητήρα είναι έγκυρος ελέγχοντας για nullptr.
pillar_men_sensor.true αν ο δείκτης του αισθητήρα δεν είναι null, και false διαφορετικά.pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false
activity_counter)Οι Pillar Men καραδοκούν στις σκιές, και πρέπει να ξέρουμε αν οι αισθητήρες έχουν ανιχνεύσει κάποια δραστηριότητα.
Θα γράψεις τη συνάρτηση activity_counter, η οποία δέχεται έναν πίνακα αισθητήρων και μια χωρητικότητα που δείχνει τον αριθμό των αισθητήρων στον πίνακα.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123
alarm_control)Δεν πρέπει να ενεργοποιεί συναγερμό κάθε αισθητήρας, παρά μόνο αν υπάρχει πραγματικός κίνδυνος.
Η συνάρτηση alarm_control διασφαλίζει ότι ένας αισθητήρας ενεργοποιεί συναγερμό μόνο αν το επίπεδο δραστηριότητάς του είναι μεγαλύτερο από 0.
Αυτή η συνάρτηση πρέπει επίσης να ελέγχει για null αισθητήρες, ώστε να αποτρέπονται καταρρεύσεις του συστήματος.
pillar_men_sensor.nullptr. Αν ο αισθητήρας είναι nullptr, επίστρεψε false.true, αλλιώς επίστρεψε false.pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true
uv_alarm
Σε αυτή την αποστολή, θα υλοποιήσεις τη συνάρτηση uv_alarm για να προσδιορίσεις αν πρέπει να ενεργοποιηθεί συναγερμός με βάση τα επίπεδα έκθεσης σε υπεριώδες φως και τη δραστηριότητα των αισθητήρων.
Η συνάρτηση uv_alarm πρέπει να χρησιμοποιεί την παρεχόμενη συνάρτηση uv_light_heuristic, η οποία λειτουργεί σε ένα διάνυσμα δεδομένων και επιστρέφει μια τιμή με βάση ορισμένα κατώφλια.
Αυτή είναι μια δοκιμαστική έκδοση του σύνθετου κώδικα που θα τρέχει σε παραγωγή, μην αλλάξεις τη διεπαφή.
Όρισε τη συνάρτηση uv_alarm στον χώρο ονομάτων speedywagon. Πρέπει:
pillar_men_sensor.false αν ο δείκτης του αισθητήρα είναι null.uv_light_heuristic, περνώντας τη διεύθυνση του πίνακα data του αισθητήρα.true αν η τιμή που επιστρέφει η uv_light_heuristic είναι μεγαλύτερη από το επίπεδο sensor->activity, αλλιώς να επιστρέφει false.Γράψου στο Exercism για να μάθεις και να κατακτήσεις C++ με 19 έννοιες100 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.