Προγραμματισμός Υπολογιστών με C++ ( 2012-13 ) 18η διάλεξη Ίων Ανδρουτσόπουλος http://www.aueb.gr/users/ion/ 1
Τι θα ακούσετε σήμερα Υλοποίηση διπλά συνδεδεμένης λίστας ακεραίων με χρήση δεικτών, δυναμικής καταχώρισης μνήμης και φωλιασμένων τάξεων. Υλοποίηση επαναλήπτη (iterator) διπλά συνδεδεμένης λίστας ακεραίων. Γενίκευση διπλά συνδεδεμένης λίστας με χρήση σχεδιότυπων τάξεων. 2
DLList list first last Διπλά συνδεδεμένη λίστα DLList::Node n1 value 10 next prev DLList::Node n2 value 30 next prev 0 DLList::Node n3 value 20 next prev 0 3
Παράδειγμα χρήσης της DLList #include "dllist.h"... int main(int argc, char* argv[]) { DLList list; try { list.putatback(30); list.putatback(40); list.putatfront(20); list.putatfront(10); catch (bad_alloc e) { cerr << argv[0] << ": Could not add some data to the list." << endl; Θα μπορούσαμε να έχουμε και μεθόδους για αφαίρεση στοιχείων από την αρχή ή το τέλος. Επίσης, να δοκιμάσουμε να αντιγράψουμε μια λίστα, να χρησιμοποιήσουμε τον τελεστή εκχώρησης κλπ. Καλές ασκήσεις... 4
#include <new>... class DLList { DLList και DLList::Node class Node { public: const int value; Node* next; Node* prev; Η Node ορίζεται μέσα στην DLList. Το πλήρες της όνομα είναι DLList::Node. Επειδή ορίζεται στο ιδιωτικό μέρος της DLList, δεν μπορεί να χρησιμοποιηθεί έξω από την DLList. Node(const int& valuein, Node* nextin = 0, Node* previn = 0) : value(valuein), next(nextin), prev(previn) { ; Ιδιωτικές μέθοδοι της DLList. Node* first; Node* last; void deletenodes(); void copynodes(const DLList& original) throw(bad_alloc); 5
DLList συνέχεια class DLList {... όπως στην προηγούμενη διαφάνεια... public: DLList() : first(0), last(0) { bool isempty() const { return (first = = 0); ~DLList() { deletenodes(); DLList(const DLList& original) throw(bad_alloc) : first(0), last(0) {copynodes(original); DLList& operator=(const DLList& right) throw(bad_alloc); void putatfront(const int valuein) throw(bad_alloc); void putatback(const int valuein) throw(bad_alloc); ; Προσθήκη στοιχείου στην αρχή ή το τέλος της λίστας. 6
Η μέθοδος DLList::putAtFront DLList::Node value 10 next prev 0 DLList first last DLList::Node value 30 next prev DLList::Node value 20 next prev 0 0 7
putatfront και putatback void DLList::putAtFront(const int& valuein) throw(bad_alloc) { Node* freshnodeptr = new Node(valueIn, first, 0); if(first!= 0) { first->prev = freshnodeptr; first = freshnodeptr; if(last = = 0) { last = first; void DLList::putAtBack(const int& valuein) throw(bad_alloc) { Node* freshnodeptr = new Node(valueIn, 0, last); if(last!= 0) { last->next = freshnodeptr; last = freshnodeptr; if(first = = 0) { first = last; 8
copynodes, deletenodes, operator= void DLList::deleteNodes() { Node* nextnodeptr = 0; while(first!= 0) { nextnodeptr = first->next; delete first; first = nextnodeptr; last = 0; void DLList::copyNodes(const DLList& original) throw(bad_alloc) { Node* originalnodeptr = original.first; while(originalnodeptr!= 0) { putatback(originalnodeptr->value); originalnodeptr = originalnodeptr->next; DLList& DLList::operator=(const DLList& right) throw(bad_alloc) { if(this == &right) { return *this; deletenodes(); copynodes(right); return *this; 9
Επαναλήπτης της DLList Ο χρήστης της DLList δεν χρειάζεται να γνωρίζει πώς έχει υλοποιηθεί η DLList. Μπορεί να σκέφτεται κάθε αντικείμενο DLList σαν μία λίστα ακεραίων: [30, 10, 70, 80, 18] Στο νοητικό μοντέλο του χρήστη, ένας επαναλήπτης θα είναι ένα βέλος, που θα μπορεί να το μετακινήσει προς το τέλος (με ++) ή την αρχή (με --) της λίστας. Με * θα μπορεί να διαβάσει τον αριθμό που βρίσκεται στη θέση της λίστας όπου δείχνει το βέλος. 10
Παράδειγμα χρήσης του επαναλήπτη #include "dllist.h"... int main(int argc, char* argv[]) { DLList list; try { list.putatback(30); list.putatback(40); list.putatfront(20); list.putatfront(10); catch (bad_alloc e) { cerr << argv[0] << ": Could not add some data to the list." << endl; for( DLList::const_iterator listiterator = list.begin(); listiterator!= list.end(); listiterator++ ) { cout << *listiterator << endl; 11
Υλοποίηση του επαναλήπτη DLList first last DLList::Node value 10 next prev DLList::Node value 30 next prev 0 DLList::const_iterator nodeptr DLList::Node value 20 next prev 0 12
class DLList {... public:... class const_iterator { ; const Node* nodeptr; public: ; Προσθήκες στη DLList const_iterator(const Node* nodeptrin = 0) : nodeptr(nodeptrin) { const const_iterator operator++(int); const const_iterator operator--(int); bool operator!=(const const_iterator& right) { return (nodeptr!= right.nodeptr); const int& operator*() const { return nodeptr->value; const const_iterator begin() const { return const_iterator(first); const const_iterator end() const { return const_iterator(0); Ονομάζουμε const_iterator την τάξη, για να θυμάται ο χρήστης ότι με επαναλήπτες αυτής της τάξης δεν μπορεί να τροποποιήσει τα περιεχόμενα της λίστας (το εγγυάται το const του δείκτη). Τα int δείχνουν ότι πρόκειται για τις επιθηματικές μορφές. Νέες μέθοδοι της DLList. 13
Οι τελεστές ++ και -- του επαναλήπτη const DLList::const_iterator DLList::const_iterator::operator++(int) { const_iterator temp(*this); // Αντίγραφο πριν την «αύξηση». nodeptr = nodeptr->next; // Μετακίνηση προς το τέλος. return temp; // Επιστρέφουμε αντίγραφο του επαναλήπτη, όπως // ήταν πριν την «αύξηση» (μετακίνηση). const DLList::const_iterator DLList::const_iterator::operator--(int) { const_iterator temp(*this); // Αντίγραφο πριν τη «μείωση». nodeptr = nodeptr->prev; // Μετακίνηση προς την αρχή. return temp; // Επιστρέφουμε αντίγραφο του επαναλήπτη, όπως // ήταν πριν τη «μείωση» (μετακίνηση). Συγκρίνετε με την επιθηματική μορφή του ++ της Point στη διάλεξη 9. 14
Γενίκευση της DLList παράδειγμα χρήσης #include "dllist.h"... int main(int argc, char* argv[]) { DLList<string> list; try { list.putatback("thirty"); list.putatback("forty"); list.putatfront("twenty"); list.putatfront("ten"); catch (bad_alloc e) { cerr << argv[0] << ": Could not add some data to the list." << endl; 15
Η DLList ως σχεδιότυπο template<typename T> class DLList { Ορισμός της DLList<T>::Node. Χωρίς νέα γραμμή template<...>. class Node { public: const T value; Node* next; Node* prev; Node(const T valuein, Node* nextin = 0, Node* previn = 0) : value(valuein), next(nextin), prev(previn) { ; Εννοείται DLList<T>::Node* next, γιατί είμαστε μέσα στο DLList<T> To DLList είναι σχεδιότυπο. Όποτε το χρησιμοποιούμε πρέπει να γράφουμε DLList<...> Node* first; Node* last; void deletenodes(); void copynodes(const DLList<T>& original) throw(bad_alloc); 16
Η DLList ως σχεδιότυπο συνέχεια template<typename T> class DLList {... public: ; DLList() : first(0), last(0) { bool isempty() const { return (first = = 0); ~DLList() { deletenodes(); DLList(const DLList<Τ>& original) throw(bad_alloc) : first(0), last(0) { copynodes(original); Το όνομα του κατασκευαστή δεν παίρνει <...>. Ούτε του καταστροφέα. DLList<Τ>& operator=(const DLList<Τ>& right) throw(bad_alloc); void putatfront(const T& valuein) throw(bad_alloc); void putatback(const T& valuein) throw(bad_alloc); 17
Η DLList ως σχεδιότυπο συνέχεια template<typename T> class DLList {... public:... class const_iterator { const Node* nodeptr; public: ; const_iterator(const Node* nodeptrin = 0) : nodeptr(nodeptrin) { const const_iterator operator++(int); const const_iterator operator--(int); bool operator!=(const const_iterator& right) { return (nodeptr!= right.nodeptr); Ορισμός της DLList<T>::const_iterator. Χωρίς νέα γραμμή template<...>. const T& operator*() const { return nodeptr->value; const const_iterator begin() const { return const_iterator(first); const const_iterator end() const { return const_iterator(0); ; 18
Ο τελεστής ++ με σχεδιότυπο template<typename T> const typename DLList<T>::const_iterator DLList<T>::const_iterator::operator++(int) { const_iterator temp(*this); nodeptr = nodeptr->next; return temp; Βοηθά το μεταγλωττιστή να καταλάβει ότι αυτό που ακολουθεί είναι επιστρεφόμενη φωλιασμένη τάξη. Χωρίς αυτό, ορισμένοι μεταγλωττιστές παράγουν προειδοποιήσεις. Ο πλήρης κώδικας (με και χωρίς σχεδιότυπα) βρίσκεται στο e-class (βλ. Έγγραφα/διαφάνειες). 19