Προγραμματισμός Υπολογιστών με C++ ( 2012-13 ) 4η διάλεξη Ίων Ανδρουτσόπουλος http://www.aueb.gr/users/ion/ 1
Τι θα ακούσετε σήμερα Δείκτες και πίνακες. Δείκτες σε σταθερές και σταθεροί δείκτες. Μεταβίβαση ορισμάτων κατά τιμή. Μεταβίβαση ορισμάτων κατά αναφορά με δείκτες. Αναφορές. Μεταβίβαση ορισμάτων κατά αναφορά με αναφορές. 2
Δυναμικοί πίνακες int* p = new int; *p = 1; // Δυναμική καταχώριση χώρου για έναν int. unsigned size; cin >> size; int* arr = new int[size]; // Δυναμική καταχώριση χώρου για // size τιμές int. Ο arr δείχνει στην // 1η θέση του χώρου. for(unsigned i = 0; i < size; i++) { arr[i] = i; // Χωρίς αστερίσκο! cout << arr[i] << endl; // Όπως με τους «απλούς» πίνακες. delete p; delete []arr; // Χωρίς [] αποδεσμεύει μόνο την 1η θέση. 3
int a[5]; Δείκτες και πίνακες // Στατική καταχώριση χώρου για πίνακα. int* b = new int[5]; // Δυναμική καταχώριση χώρου για πίνακα. a[4] = 10; b[4] = 10; *a = 2; // Ισοδύναμο του a[0] = 2. *b = 2; // Ισοδύναμο του b[0] = 2. *(a + 1) = 4; // Ισοδύναμο του a[1] = 4. *(b + 1) = 4; // Ισοδύναμο του b[1] = 4. b = a; int* c = a; X a = b; // OK. Χάσαμε όμως τη διεύθυνση του // δυναμικού πίνακα. Πώς θα κάνουμε delete[]; // Τα ονόματα πινάκων είναι ουσιαστικά δείκτες. // Τα ονόματα πινάκων είναι δείκτες που δεν // επιτρέπεται να δείξουν αλλού. 4
Παράδειγμα με δείκτες #include <iostream> using namespace std; int main() { unsigned howmany; cin >> howmany; int* storage = new int[howmany]; for(unsigned i = 0; i < howmany; i++) { cin >> storage[i]; for(unsigned i = 0; i < howmany; i++) { cout << i << ": " << storage[i] << endl; delete []storage; 5
Δείκτες σε σταθερές const int i2 = 10; X int* p2 = &i2; // Θα μπορούσα να αλλάξω το i2 // μέσω του p2, με *p2 = 20. const int* p3 = &i2; // OK. Υπόσχομαι να μην αλλάξω αυτό // στο οποίο δείχνει ο p3. cout << *p3; // OK. Τυπώνει 10. X *p3 = 20; // Δεν επιτρέπεται. Δείκτης σε const. int i1 = 10; // Το i1 δεν είναι σταθερά. const int* p4 = &i1; // Επιτρέπεται, αλλά δεν μπορώ να // αλλάξω το i1 μέσω του p4. i1 = 20; // OK. X *p4 = 20; // Δεν επιτρέπεται. 6
Σταθεροί δείκτες int j1 = 10, j2 = 20; int* const p5 = &j1; // Σταθερός δείκτης στο j1. *p5 = 30; // ΟΚ, αλλάζει το j1. X p5 = &j2; // Ο p5 δεν μπορεί να δείξει αλλού. const int* const p6 = &j1; // Σταθερός δείκτης σε σταθερά. X p6 = &j2; // Ο p6 είναι σταθερός: δεν μπορεί να // δείξει αλλού. X *p6 = 30; // Ο p6 δείχνει σε σταθερά: δεν μπορεί να // αλλάξει τα περιεχόμενα της θέσης // μνήμης στην οποία δείχνει. 7
Μεταβίβαση ορισμάτων κατά τιμή int incr(int arg) { // To arg συμπεριφέρεται σαν τοπική μεταβλητή. int k = 5; // Καμία σχέση με το k της main. arg++; // Αλλάζει μόνο το τοπικό arg. return arg; // Επιστρέφεται η τιμή του τοπικού arg. int main() { int k = 0, i = 1, j = 0; j = incr(i); // Το arg της incr παίρνει την τιμή του i της main. // Αλλάζει η τιμή του j, όχι του i. cout << k << ", " << i << ", " << j; // Τυπώνει "0, 1, 2". 8
Μεταβ/ση ορισμάτων κατά αναφορά με δείκτες int incr(int* arg) { // Το όρισμα είναι τώρα δείκτης, δηλαδή // απαιτείται ως τιμή του μια διεύθυνση. (*arg)++; // Αυξάνει το περιεχόμενο της θέσης // όπου δείχνει. // Ο arg είναι τοπική μεταβλητή, αλλά // δείχνει στo i της main. return *arg; // Επιστρέφει το νέο περιεχόμενο της θέσης. int main() { int i = 1, j = 0; j = incr(&i); // Το arg της incr παίρνει ως // τιμή τη διεύθυνση του i. // Αλλάζει και το j και το i. cout << i << ", " << j; // Τυπώνει "2, 2". 9
Αναφορές int i = 10; int& r = i; // Το r είναι αναφορά στο i (ψευδώνυμο του i). cout << r; // Τυπώνει 10. r++; // Αυξάνει το i. cout << i; // Τυπώνει 11. int* p = &i; // Δείκτης στο i. (*p)++; // Αυξάνει το i. cout << *p; // Τυπώνει 12. X int& r2; // Δεν επιτρέπεται. int* p2; // Επιτρέπεται. 10
Αναφορές σε σταθερές const int i2 = 10; X int& r2 = i2; // Θα μπορούσα να αλλάξω το i2 // μέσω του r2, με r2 = 20. const int& r3 = i2; // OK, υπόσχομαι να μην αλλάξω αυτό // στο οποίο αναφέρεται το r3. cout << r3; // OK. Τυπώνει 10. X r3 = 20; // Δεν επιτρέπεται. Αναφορά σε const. int i1 = 20; const int& r4 = i1; // Επιτρέπεται, αλλά δεν μπορώ να // αλλάξω το i1 μέσω του r4. 11
Μεταβίβαση ορισμάτων κατά αναφορά με αναφορές int incr(int& arg) { // To arg είναι αναφορά σε ακέραια // μεταβλητή. // Το arg είναι τοπική αναφορά, αλλά // αναφέρεται στο i της main. arg++; // Αυξάνει τη μεταβλητή στην οποία // αναφέρεται το arg. return arg; // Επιστρέφει τη νέα τιμή της μεταβλητής. int main() { int i = 1, j = 0; j = incr(i); // Αλλάζει και το j και το i. cout << i << ", " << j; // Τυπώνει "2, 2". 12
Συμβουλές Κατά τη μεταβίβαση μεγάλων δομών δεδομένων, η μεταβίβαση κατά τιμή είναι αργή,γιατί αντιγράφει τις δομές. int myfun(vector<int> v); // Κατά τιμή. Αργό. Π.χ. κατά // την κλήση myfun(somevector), το τοπικό vector v της // myfun γίνεται αρχικά αντίγραφο του somevector. Πρέπει να // αντιγραφούν όλα τα περιεχόμενα του somevector στο v. Στη μεταβίβαση κατά αναφορά (με χρήση δεικτών ή αναφορών) αντιγράφεται μόνο η διεύθυνση της δομής, πιο γρήγορο. int myfun(vector<int>& v); // Κατά αναφορά. Γρήγορο. // Π.χ. κατά την κλήση myfun(somevector), η τοπική αναφορά // v της myfun γίνεται απλά ψευδώνυμο του somevector. 13
Συμβουλές συνέχεια int myfun(vector<int>& v); // Κατά αναφορά. Γρήγορο, // αλλά π.χ. κατά την κλήση myfun(somevector) έχει η myfun τη // δυνατότητα να αλλάξει τa περιεχόμενα του somevector. int myfun(const vector<int>& v); // Κατά αναφορά. Γρήγορο // και π.χ. κατά την κλήση myfun(somevector) δεν μπορεί η // myfun να αλλάξει τa περιεχόμενα του somevector. Με μεγάλες δομές δεδομένων, αντί για μεταβίβαση κατά τιμή χρησιμοποιήστε μεταβίβαση κατά αναφορά (με δείκτες ή αναφορές) και χρησιμοποιήστε το const αν θέλετε να εξασφαλίσετε ότι η καλούμενη συνάρτηση δεν θα μπορεί να αλλάξει αυτό που της μεταβιβάζουμε ως όρισμα. 14
Βάζουμε const όπου μπορούμε void f(string& arg) { cout << arg; void g(const string& arg) { // Υπόσχεται να μην αλλάξει το όρισμα. cout << arg; int main() { string s1 = "hello"; // Το s1 μπορεί να αλλάξει. const string s2 = "world"; // Το s2 δεν μπορεί να αλλάξει. f(s1); // OK. // f(s2); // Λάθος. Η f δεν εγγυάται ότι δεν θα αλλάξει το s2. g(s1); g(s2); // OK και τα δύο. g(s1 + s2); // Δημιουργείται προσωρινή σταθερά const string // για το άθροισμα, που μεταβιβάζεται κατά αναφορά. // f(s1 + s2); // Λάθος. Δεν εγγυάται ότι δεν θα αλλάξει την // προσωρινή σταθερά του αθροίσματος. 15
const και μεταβίβαση κατά τιμή void f(string arg) { cout << arg; void g(const string arg) { // Απλά το τοπικό arg είναι σταθερά. cout << arg; // Το const σε όρισμα που μεταβιβάζεται κατά // τιμή δεν έχει μεγάλη χρησιμότητα. int main() { string s1 = "hello"; // Το s1 μπορεί να αλλάξει. const string s2 = "world"; // Το s2 δεν μπορεί να αλλάξει. f(s1); // OK. f(s2); // ΟΚ. Μεταβίβαση κατά τιμή, οπότε δεν υπάρχει // κίνδυνος να αλλάξει το ίδιο το s2. g(s1); g(s2); // OK και τα δύο. g(s1 + s2); // ΟΚ. Η προσωρινή σταθερά του αθροίσματος // s1 + s2 μεταβιβάζεται κατά τιμή. f(s1 + s2); // OK. Μεταβίβαση κατά τιμή, κανένας κίνδυνος να 16 // αλλάξει η προσωρινή σταθερά του αθροίσματος.