1 ΗΓλώσσαΠρογραµµατισµού C++ (The C++ Programming Language) ηµήτριος Κατσαρός, Ph.D. Ελένη Τουσίδου, Ph.D. Χειµώνας 2006 ιάλεξη 4η
2 Ιστοσελίδα του µαθήµατος http://skyblue.csd.auth.gr/~dimitris/courses/cpp_fall06.htm Θα τοποθετούνται οι διαφάνειες του επόµενου µαθήµατος Επικοινωνία: dimitris@skyblue.csd.auth.gr etousido@uth.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, * Η µεταβλητή δείκτη γίνεται "dereferenced" Σηµαίνει: Πάρε τα δεδοµένα στα οποία δείχνει ο 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 Μεταβλητές πίνακα είκτες Μια διεύθυνση όπως η 0x8f4ffff4 θεωρείται ως σταθερά δείκτη. Μεταβλητή πίνακα int a[10]; Κάτι περισσότερο από µεταβλητή δείκτη Οτύποςτηςείναι int * const Ο πίνακας δεσµεύτηκε ήδη στην µνήµη Η µεταβλητή a ΠΡΕΠΕΙ να δείχνει εκεί πάντα! εν µπορεί να αλλάξει! Σε αντιδιαστολή µε τους συνήθεις δείκτες Που συνήθως (και τυπικά) αλλάζουν
38 Μεταβίβαση Πινάκων: Παράδειγµα # include <iostream.h> const int MAX=5; void main( ) { void centimize(double*,int); double varray[max] = {10.0, 43.1, 95.9, 59.7, 87.3}; centimize(varray,max); // δεν χρειάζεται το &, είναι διεύθυνση ήδη for (int j=0; j<max; j++) cout << endl << varray[ << j << ]= << varray[j] << εκατοστά ; } void centimize(double *ptrd, int size) { for (int j=0; j<size; j++) *ptrd++ *= 2.54; } // ή double[ ] ptrd // ερµηνεύεται ως *(ptrd++)
39 υναµικοί πίνακες Περιορισµοί των πινάκων Πρέπει να καθορίσουµε πρώτα το µέγεθός τους Ίσως να µην το γνωρίζουµε µέχρι να εκτελεστεί το πρόγραµµα! Πρέπει να υπολογίζουµε το µέγιστο µήκος που θα απαιτηθεί Μερικές φορές OK, µερικές όχι Σπατάλη µνήµης υναµικοί πίνακες Μπορεί να µεγαλώνει και να µικραίνει κατά βούληση
40 ηµιουργία δυναµικών πινάκων Πολύ απλό! Χρήση του τελεστή new υναµική δέσµευση µε µεταβλητή δείκτη Τους θεωρούµε όπως και τους συνηθισµένους πίνακες Παράδειγµα: typedef double * DoublePtr; DoublePtr d; d = new double[10]; //Το µέγεθος σε αγκύλες ηµιουργεί τη δυναµικά δεσµευµένη µεταβλητή πίνακα d, µε 10 στοιχεία, βασικού τύπου double
41 ιαγραφή δυναµικών πινάκων Αφού δηµιουργούνται/δεσµεύονται δυναµικά κατά τη διάρκεια εκτέλεσης του προγράµµατος Θα πρέπει να υπάρχει η δυνατότητα καταστροφής τους κατά τη διάρκεια εκτέλεσης του προγράµµατος Θυµηθείτε ξανά το απλό παράδειγµα: d = new double[10]; //Επεξεργασία delete [] d; Αποδεσµεύει τη µνήµη που διατέθηκε για τον δυναµικό πίνακα Οι αγκύλες υπονοούν πίνακα ΠΡΟΣΟΧΗ: ο d εξακολουθεί να δείχνει εκεί! Θα πρέπει να θέτουµε d = NULL ή d = 0;
42 Συνάρτηση που επιστρέφει πίνακα Τύπος δεδοµένων πίνακα δεν µπορεί να αποτελεί επιστροφή συνάρτησης Παράδειγµα: int [] somefunction(); // ΜΗ ΕΓΚΥΡΟ! Επιστρέφουµε δείκτη στον τύπο δεδοµένων του πίνακα: int* somefunction(); // ΕΓΚΥΡΟ!
43 Αριθµητική δεικτών Μπορούµε να εκτελέσουµε αριθµητική πάνω στους δείκτες Αριθµητική ιευθύνσεων Παράδειγµα: typedef double* DoublePtr; DoublePtr d; d = new double[10]; Το d περιέχει τη διεύθυνση του d[0] Το d + 1 αποτιµάται στη διεύθυνση του d[1] Το d + 2 αποτιµάται στη διεύθυνση του d[2]
44 Παράδειγµα επιστροφής πίνακα int* doubler(int a[], int size); int main( ) { int a[]={1, 2, 3, 4, 5}; int *b; b = doubler(a, 5);.. delete[] b; return 0; } int* doubler(int a[], int size) { int *temp = new int[size]; for (int i=0; i<size; i++) temp[i] = 2*a[i]; return temp; }
45 Εναλλακτική διαχείριση πινάκων Χρήση αριθµητικής δεικτών! Σάρωση πίνακα χωρίς χρήση δεικτών: for (int i = 0; i < arraysize; i++) cout << *(d + i) << " " ; Ισοδύναµο µε το παρακάτω: for (int i = 0; i < arraysize; i++) cout << d[i] << " " ; Μόνο πρόσθεση/αφαίρεση πάνω στους δείκτες ΌΧΙ πολλαπλασιασµό, διαίρεση Μπορούµεναχρησιµοποιήσουµε τους τελεστές ++ και -- στους δείκτες
46 Σταθερές και Μεταβλητές είκτη Έστω η δήλωση: int intarray[5]; Ηδήλωση*(intarray++); δεν είναι επιτρεπτή γιατί δεν µπορείτε να αυξήσετε µια σταθερά. Η διεύθυνση intarray περιέχει τον πίνακά σας και δεν µπορεί να αλλάξει αυτό µέχρι να τερµατίσει το πρόγραµµα. Αντιθέτως: int intarray[ ] = {31, 54, 77, 52, 93} int *ptrint; ptrint = intarray; for (int j=0; j<5; j++) cout << endl << *(ptrint++); Ο ptrint είναι µεταβλητή και όχι σταθερά και για αυτό το λόγο µπορεί να αυξηθεί.
47 Πολυδιάστατοι δυναµικοί πίνακες Βεβαίως µπορούµεναέχουµε! Θυµηθείτε: είναι πίνακες από πίνακες Οι ορισµοί τύπων µας βοηθούµενατο διαπιστώσουµε: typedef int* IntArrayPtr; IntArrayPtr *m = new IntArrayPtr[3]; ηµιουργεί πίνακα από τρεις δείκτες Θα φτιάξουµε κώδικαώστεοκαθέναςτουςνα δεσµεύσει χώρο για 4 ints for (int i = 0; i < 3; i++) m[i] = new int[4]; Προκύπτει ένας δυναµικός πίνακας 3Χ4!
48 Περίληψη Ο δείκτης είναι µια διεύθυνση µνήµης Παρέχει έµµεση αναφορά σε µια µεταβλητή υναµικές µεταβλητές ηµιουργούνται και καταστρέφονται κατά τη διάρκεια εκτέλεσης του προγράµµατος Freestore Χώρος µνήµης για αποθήκευση δυναµικών µεταβλητών υναµικά δεσµευµένοι πίνακες Το µέγεθός τους προσδιορίζεται κατά τη διάρκεια εκτέλεσης του προγράµµατος