1 Η Γλώσσα Προγραµµατισµού C++ (The C++ Programming Language) ηµήτριος Κατσαρός, Ph.D. Χειµώνας 2005 ιάλεξη 4η
2 Ιστοσελίδα του µαθήµατος http://skyblue.csd.auth.gr/~dimitris/courses/cpp_fall05.htm Θα τοποθετούνται οι διαφάνειες του επόµενου µαθήµατος Επικοινωνία: dimitris@skyblue.csd.auth.gr
3 Περιεχόµενα είκτες και υναµικοί Πίνακες
4 Στόχοι εκµάθησης είκτες Μεταβλητές τύπου δείκτη ιαχείριση µνήµης υναµικοί πίνακες ηµιουργία και χρήση Αριθµητική πινάκων
5 Εισαγωγή στους δείκτες Ορισµός δείκτη: ιεύθυνση µνήµης µιας µεταβλητής Θυµηθείτε: η µνήµη διαιρείται σε Αριθµηµένες θέσεις µνήµης Οι διευθύνσεις χρησιµοποιούνται ως ονόµατα µεταβλητών Έχουµε ήδη χρησιµοποιήσει δείκτες! Παράµετροι που καλούνται µε αναφορά (Call-byreference) Περνάει η διεύθυνση του πραγµατικού ορίσµατος
6 Μεταβλητές τύπου δείκτη Οι δείκτες έχουν τύπο Μπορούν να αποθηκεύσουν δείκτη σε µεταβλητή Όχι int, double, κ.τ.λ. Αλλά: Ένας POINTER σε int, double, κ.τ.λ.! Παράδειγµα: double *p; p δηλώνεται ως µεταβλητή τύπου δείκτη σε double Μπορεί να κρατήσει δείκτες σε µεταβλητές τύπου double Όχι σε άλλους τύπους!
7 ήλωση µεταβλητών τύπου δείκτη Οι δείκτες δηλώνονται όπως και οι άλλοι τύποι Προηγείται το "*" πριν το όνοµα της µεταβλητής Παράγει δείκτη σε αυτόν τον τύπο Το "*" να προηγείται κάθε µεταβλητής int *p1, *p2, v1, v2; Οι p1, p2 είναι δείκτες σε µεταβλητές int Οι v1, v2 είναι συνηθισµένες µεταβλητές τύπου int
8 ιευθύνσεις και αριθµοί Ο δείκτης είναι µια διεύθυνση Η διεύθυνση είναι ένας ακέραιος Ο δείκτης ΕΝ είναι ακέραιος! Η C++ εξαναγκάζει τους δείκτες να χρησιµοποιούνται ως διευθύνσεις εν µπορούν να χρησιµοποιηθούν ως αριθµοί Παρόλο που στην πραγµατικότητα είναι ένας αριθµός
9 εικτοδότηση Ορολογία, όψη Μιλάµε πάντα για δεικτοδότηση, όχι διευθυνσιοδότητη Η µεταβλητή δείκτη δείχνει σε κανονική µεταβλητή Κάνει ευκολότερη την κατανόηση Βλέπουµε αναφορές στη µνήµη
10 είχνοντας σε int *p1, *p2, v1, v2; p1 = &v1; Θέτει τη µεταβλητή δείκτη p1 να δείχνει στη µεταβλητή τύπου int v1 Τελεστής, & Προσδιορίζει τη διεύθυνση µιας µεταβλητής Θα το διαβάζαµε ως εξής: η p1 ισούται µε τη διεύθυνση της v1 ή η p1 δείχνει στη v1
11 είχνοντας σε Θυµηθείτε: int *p1, *p2, v1, v2; p1 = &v1; Τώρα έχουµε δύο τρόπους για να αναφερόµαστε στη v1: Τη µεταβλητή v1: cout << v1; ιαµέσου του δείκτη p1: cout *p1; Τελεστής dereference, * Η µεταβλητή δείκτη γίνεται "derereferenced" Σηµαίνει: Πάρε τα δεδοµένα στα οποία δείχνει ο p1
12 Παράδειγµα χρήσης δείκτη Θεωρήστε το παράδειγµα κώδικα: v1 = 0; p1 = &v1; *p1 = 42; cout << v1 << endl; cout << *p1 << endl; Παράγει την έξοδο: 42 42 Οι p1 και v1 αναφέρονται στην ίδια µεταβλητή
13 Τελεστής & Ο τελεστής διεύθυνσης του Χρησιµοποιείται επίσης για να καθορίζει παραµέτρους call-by-reference εν είναι σύµπτωση! Θυµηθείτε: οι παράµετροι call-by-reference περνούν τη διεύθυνση του πραγµατικού ορίσµατος Η δυο χρήσεις του τελεστή είναι στενά συνδεδεµένες
14 Ανάθεση δεικτών Οι µεταβλητές δείκτη µπορούν να ανατεθούν : int *p1, *p2; p2 = p1; Αναθέτει ένα δείκτη σε έναν άλλο Κάνε τον p2 να δείχνει εκεί όπου δείχνει ο p1 Να µην συγχέεται µε το : *p1 = *p2; Αναθέτει την τιµή που δείχνεται από τον p1, στην τιµή που δείχνεται από τον p2
15 Παράδειγµα ανάθεσης δεικτών
16 Τελεστής new Αφού οι δείκτες αναφέρονται σε µεταβλητές εν υπάρχει πραγµατική ανάγκη να έχουµε standard προσδιοριστή Μπορούµε να δεσµεύουµε µεταβλητές δυναµικά Ο τελεστής new δηµιουργεί µεταβλητές Όχι προσδιοριστές για να αναφερόµαστε σε αυτές Μόνο µε τον δείκτη! p1 = new int; ηµιουργεί νέα µεταβλητή χωρίς όνοµα, και αναθέτει τον p1 να δείχνει σ αυτή Μπορούµε να την προσπελάσουµε µε το *p1 Χρήση της, όπως και οι συνηθισµένες µεταβλητές
17 Παράδειγµα χρήσης δεικτών (1/2)
18 Παράδειγµα χρήσης δεικτών (2/2)
19 ιαχείριση
20 Περισσότερα για τον τελεστή new ηµιουργεί νέα δυναµική µεταβλητή Επιστρέφει δείκτη στη νέα µεταβλητή Εάν ο τύπος είναι τύπος class (προσεχείς διαλέξεις): Καλείται ο constructor για το νέο αντικείµενο Κλήση διαφορετικού constructor ανάλογα µε τα ορίσµατα αρχικοποίησης: MyClass *mcptr; mcptr = new MyClass(32.0, 17); Μπορεί να αρχικοποιήσει και non-class τύπους: int *n; n = new int(17); //Initializes *n to 17
21 είκτες και συναρτήσεις Οι δείκτες είναι κανονικοί τύποι Μπορούν να χρησιµοποιηθούν όπως και οι άλλοι τύποι Μπορούν να είναι παράµετροι συναρτήσεων Μπορούν να επιστρέφονται από συναρτήσεις Παράδειγµα: int* findotherpointer(int* p); Αυτή η δήλωση συνάρτησης: Έχει µια παράµετρο δείκτη σε int Επιστρέφει µεταβλητή δείκτη σε int
22 ιαχείριση µνήµης Σωρός (Heap) Αποκαλείται επίσης και freestore Χρησιµοποιείται για τις δυναµικά δεσµευµένες µεταβλητές Όλες οι νέες δυναµικές µεταβλητές καταναλώνουν µνήµη στο freestore Εάν είναι πάρα πολλές θα µπορούσε να εξαντληθεί όλη τη µνήµη του freestore Μελλοντικές λειτουργίες new θα αποτύχουν εάν το freestore είναι πλήρες
23 Έλεγχος επιτυχίας του new (1/2) Παλιοί µεταγλωττιστές: Έλεγχος εάν επιστράφηκε null από την κλήση στο new: int *p; p = new int; if (p == NULL) { cout << "Error: Insufficient memory.\n"; exit(1); } Εάν πέτυχε η new, το πρόγραµµα συνεχίζει
24 Έλεγχος επιτυχίας του new (2/2) Μερικοί νεώτεροι µεταγλωττιστές : Εάν η κλήση στη new αποτύχει: Το πρόγραµµα τερµατίζεται αυτόµατα Παράγει µήνυµα λάθους Η χρήση του ελέγχου για NULL παραµένει καλή διαχρονική πρακτική
25 Μέγεθος του freestore Κυµαίνεται ανάλογα µε την υλοποίηση Συνήθως είναι µεγάλο Τα περισσότερα προγράµµατα δεν θα χρησιµοποιήσουν όλη τη µνήµη ιαχείριση µνήµης Καλή πρακτική Στέρεα αρχή του software engineering Η µνήµη είναι πεπερασµένη Ανεξάρτητα από το µέγεθός της!
26 Τελεστής delete Απο-δεσµεύει τη δυναµική µνήµη Όταν δεν χρειάζεται πλέον Επιστρέφει τη µνήµη στο freestore Παράδειγµα: int *p; p = new int(5); //Some processing delete p; Αποδεσµεύει τη δυναµική µνήµη που δείχνεται από τον δείκτη p ηλαδή καταστρέφει τη µνήµη
27 Αιωρούµενοι (dangling) δείκτες delete p; Καταστρέφει τη δυναµική µνήµη Αλλά ο p εξακολουθεί να δείχνει εκεί! Αποκαλείται αιωρούµενος (dangling) δείκτης Εάν ο p χρησιµοποιηθεί (π.χ., *p) Απρόβλεπτα αποτελέσµατα! Συχνά καταστροφικά! Αποφύγετε τους αιωρούµενους δείκτες Αναθέστε στον δείκτη τιµή NULL αµέσως µετά τη delete: delete p; p = NULL;
28 υναµικές και αυτόµατες µεταβλητές υναµικές µεταβλητές (dynamic variables) ηµιουργούνται µε τον τελεστή new ηµιουργούνται και καταστρέφονται κατά την διάρκεια εκτέλεσης του προγράµµατος Τοπικές µεταβλητές (local variables) ηλώνονται µέσα στον ορισµό συνάρτησης εν είναι δυναµικές ηµιουργούνται όταν καλείται η συνάρτηση Καταστρέφονται όταν τερµατίζεται η συνάρτηση Συχνά αποκαλούνται αυτόµατες automatic µεταβλητές
29 Ορισµός τύπων δείκτη Μπορούµε να ονοµατίσουµε τύπους δείκτη Για να δηλώνουµε τους δείκτες όπως και τις άλλες µεταβλητές Εξαλείφουµε την ανάγκη για το * στη δήλωση του δείκτη typedef int* IntPtr; Ορίζει ένα alias για νέο τύπο Εξετάστε τις δηλώσεις: IntPtr p; int *p; Είναι ισοδύναµες
30 Παγίδα: είκτες Call-by-value Συµπεριφορά: λεπτή και προβληµατική Εάν η συνάρτηση αλλάξει την παράµετρο δείκτη αυτή καθ εαυτή αλλάζει µόνο το τοπικό αντίγραφο είτε το παράδειγµα
31 Παράδ. δείκτη call-by-value (1/2)
32 Παράδ. δείκτη call-by-value (1/2)
33 Ερµηνεία call-by-value: sneaky(p);
34 υναµικοί πίνακες Μεταβλητές πίνακα Στην πραγµατικότητα µεταβλητές δείκτη! Τυπικός πίνακας Σταθερό µέγεθος υναµικός πίνακας Το µέγεθός του δεν προσδιορίζεται τη στιγµή προγραµµατισµού Καθορίζεται όταν εκτελείται το πρόγραµµα
35 Μεταβλητές πίνακα Θυµηθείτε: οι πίνακες αποθηκεύονται στη µνήµη σε συνεχόµενες θέσεις Η µεταβλητή πίνακα αναφέρεται στο πρώτο στοιχείο (indexed variable) Έτσι, η µεταβλητή πίνακα είναι ένα είδος µεταβλητής δείκτη! Παράδειγµα: int a[10]; int * p; Οι a και p είναι και οι δυο µεταβλητές δείκτη!
36 Μεταβλητές πίνακα είκτες Θυµηθείτε το προηγούµενο παράδειγµα: int a[10]; typedef int* IntPtr; IntPtr p; Οι a και p είναι µεταβλητές δείκτη Μπορούµε να εκτελέσουµε αναθέσεις: p = a; // Έγκυρο. Η p τώρα δείχνει εκεί όπου δείχνει η a Στο πρώτο στοιχείο του πίνακα a a = p; // Μη έγκυρο! Ο δείκτης-πίνακας είναι CONSTANT δείκτης!
37 Μεταβλητές πίνακα είκτες Μεταβλητή πίνακα int a[10]; Κάτι περισσότερο από µεταβλητή δείκτη Ο τύπος της είναι const int * Ο πίνακας δεσµεύτηκε ήδη στην µνήµη Η µεταβλητή a ΠΡΕΠΕΙ να δείχνει εκεί πάντα! εν µπορεί να αλλάξει! Σε αντιδιαστολή µε τους συνήθεις δείκτες Που συνήθως (και τυπικά) αλλάζουν
38 υναµικοί πίνακες Περιορισµοί των πινάκων Πρέπει να καθορίσουµε πρώτα το µέγεθός τους Ίσως να µην το γνωρίζουµε µέχρι να εκτελεστεί το πρόγραµµα! Πρέπει να υπολογίζουµε το µέγιστο µήκος που θα απαιτηθεί Μερικές φορές OK, µερικές όχι Σπατάλη µνήµης υναµικοί πίνακες Μπορεί να µεγαλώνει και να µικραίνει κατά βούληση
39 ηµιουργία δυναµικών πινάκων Πολύ απλό! Χρήση του τελεστή new υναµική δέσµευση µε µεταβλητή δείκτη Τους θεωρούµε όπως και τους συνηθισµένους πίνακες Παράδειγµα: typedef double * DoublePtr; DoublePtr d; d = new double[10]; //Το µέγεθος σε αγκύλες ηµιουργεί τη δυναµικά δεσµευµένη µεταβλητή πίνακα d,µε 10 στοιχεία, βασικού τύπου double
40 ιαγραφή δυναµικών πινάκων Αφού δηµιουργούνται/δεσµεύονται δυναµικά κατά τη διάρκεια εκτέλεσης του προγράµµατος Θα πρέπει να υπάρχει η δυνατότητα καταστροφής τους κατά τη διάρκεια εκτέλεσης του προγράµµατος Θυµηθείτε ξανά το απλό παράδειγµα: d = new double[10]; //Επεξεργασία delete [] d; Αποδεσµεύει τη µνήµη που διατέθηκε για τον δυναµικό πίνακα Οι αγκύλες υπονοούν πίνακα ΠΡΟΣΟΧΗ: ο d εξακολουθεί να δείχνει εκεί! Θα πρέπει να θέτουµε d = NULL ή d = 0;
41 Συνάρτηση που επιστρέφει πίνακα Τύπος δεδοµένων πίνακα δεν µπορεί να αποτελεί επιστροφή συνάρτησης Παράδειγµα: int [] somefunction(); // ΜΗ ΕΓΚΥΡΟ! Επιστρέφουµε δείκτη στον τύπο δεδοµένων του πίνακα: int* somefunction(); // ΕΓΚΥΡΟ!
42 Αριθµητική δεικτών Μπορούµε να εκτελέσουµε αριθµητική πάνω στους δείκτες Αριθµητική ιευθύνσεων Παράδειγµα: typedef double* DoublePtr; DoublePtr d; d = new double[10]; Το d περιέχει τη διεύθυνση του d[0] Το d + 1 αποτιµάται στη διεύθυνση του d[1] Το d + 2 αποτιµάται στη διεύθυνση του d[2]
43 Εναλλακτική διαχείριση πινάκων Χρήση αριθµητικής δεικτών! Σάρωση πίνακα χωρίς χρήση δεικτών: for (int i = 0; i < arraysize; i++) cout << *(d + I) << " " ; Ισοδύναµο µε το παρακάτω: for (int i = 0; i < arraysize; i++) cout << d[i] << " " ; Μόνο πρόσθεση/αφαίρεση πάνω στους δείκτες ΌΧΙ πολλαπλασιασµό, διαίρεση Μπορούµε να χρησιµοποιήσουµε τους τελεστές ++ και -- στους δείκτες
44 Πολυδιάστατοι δυναµικοί πίνακες Βεβαίως µπορούµε να έχουµε! Θυµηθείτε: είναι πίνακες από πίνακες Οι ορισµοί τύπων µας βοηθούµε να το διαπιστώσουµε: typedef int* IntArrayPtr; IntArrayPtr *m = new IntArrayPtr[3]; ηµιουργεί πίνακα από τρεις δείκτες Θα φτιάξουµε κώδικα ώστε ο καθένας τους να δεσµεύσει χώρο για 4 ints for (int i = 0; i < 3; i++) m[i] = new int[4]; Προκύπτει ένας δυναµικός πίνακας 3Χ4!
45 Περίληψη Ο δείκτης είναι µια διεύθυνση µνήµης Παρέχει έµµεση αναφορά σε µια µεταβλητή υναµικές µεταβλητές ηµιουργούνται και καταστρέφονται κατά τη διάρκεια εκτέλεσης του προγράµµατος Freestore Χώρος µνήµης για αποθήκευση δυναµικών µεταβλητών υναµικά δεσµευµένοι πίνακες Το µέγεθός τους προσδιορίζεται κατά τη διάρκεια εκτέλεσης του προγράµµατος