Μία κλάση τύπου iterator έχει την δυνατότητα διάτρεξης μιας άλλης κλάσης που αντιπροσωπεύει μία δομή δεδομένων (τη γνωρίσαμε ως container). Η κλάση iterator γνωρίζει την εσωτερική δομή της κλάσης τύπου container και υλοποιεί τον αλγόριθμο για να διαπεράσει το σύνολο των στοιχείων της. Κάθε iterator περιέχει κατ'ελάχιστο ένα δείκτη προς το τρέχον στοιχείο προς διάτρεξη του container στον οποίο αναφέρεται. Όλοι οι iterators ανεξάρτητα από τον τύπο τους υποστηρίζουν τις λειτουργίες:
T::iterator it(other_it)
: copy-constructorit = other_it
: copy-assignment++it
ή it++
: μετακίνηση κατά μία θέση μέσα στον container και επιστροφή ενός iterator που δείχνει στην τρέχουσα θέση ή στην προηγούμενη θέση του container αντιστοίχως.==
και !=
που υλοποιούν τη σύγκριση ως προς την ισότητα/ανισότητα δύο iterators. Δύο iterators είναι ίσοι εάν δείχνουν στην ίδια θέση του ιδίου containerΟι κατηγορίες (τύποι)των iterator είναι οι παρακάτω. Ένας iterator μπορεί να ανήκει σε περισσότερους από ένα τύπους.
#include <vector> #include <iostream> #define SIZE 10 using namespace std; int main() { vector<int> ints; for(int i=0; i<10; i++) ints.push_back(SIZE-i); for(vector<int>::const_iterator it=ints.cbegin(); it!=ints.cend(); it++) cout << *it << " "; cout << endl; }
#include <vector> #include <iostream> #define SIZE 10 using namespace std; int main() { vector<int> ints; for(int i=0; i<10; i++) ints.push_back(SIZE-i); for(vector<int>::iterator it=ints.begin(); it!=ints.end(); it++) { *it += 10; // we modify container here cout << *it << " "; } cout << endl; for(vector<int>::const_iterator it=ints.cbegin(); it!=ints.cend(); it++) { // *it += 10; // this is not allowed for a const_iterator (only input iterator) cout << *it << " "; } }
++it
, it++
υποστηρίζουν και τις πράξεις --it
, it--
. Bidirectional Iterators είναι όλοι οι iterators με εξαίρεση εκείνούς που ανήκουν σε Unordered Associative Containers και οι iterators του container forward_list (απλά συνδεδεμένη λίστα). Παραδείγματα:#include <iostream> #include <vector> #define SIZE 10 using namespace std; int main() { vector<int> ints(SIZE); for(int i=0; i<SIZE; i++) ints[i] = i+1; vector<int>::const_iterator it = ints.cend(); // starting from the end of the container while(true) { --it; // move to the previous element cout << *it << " "; if(it == ints.cbegin()) // stop when we reach the first element break; } cout << endl; }
Όταν μία κλάση υποστηρίζει bidirectional iterators τότε εκτός από τις μεθόδους begin, end (και τις συμμετρικές τους για const iterators cbegin, cend), διαθέτει και τις μεθόδους rbegin, rend (και τις συμμετρικές τους crbegin,crend). Οι iterators που επιστρέφονται από τις μεθόδους αυτές έχουν την ιδιότητα να διατρέχουν τη δομή του container από το τέλος προς την αρχή. Το παρακάτω παράδειγμα είναι αποκαλυπτικό της χρήσης αυτών των iterator. Σημειώστε ότι η διάτρεξη της λίστας από το τέλος προς την αρχή γίνεται με τη βοήθεια του τελεστή ++ , δηλαδή σε κάθε βήμα αυξάνει ο reverse_iterator κατά ένα.
#include <iostream> #include <vector> #define SIZE 10 int main () { std::vector<int> myvector (SIZE); // SIZE default-constructed ints int i=0; for (std::vector<int>::reverse_iterator rit = myvector.rbegin(); rit!= myvector.rend(); ++rit) *rit = ++i; std::cout << "myvector contains:"; for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) std::cout << ' ' << *it; std::cout << '\n'; return 0; }
#include <iostream> #include <vector> #define SIZE 10 using namespace std; int main() { vector<int> ints(SIZE); for(int i=0; i<SIZE; i++) ints[i] = i+1; vector<int>::const_iterator it = ints.cend(); // starting past the end of the container while(it > ints.cbegin()) { it -= 2; // move back by 2 elements cout << *it << " "; if(it <= ints.cbegin()) // stop when we reach or we have passed the first element break; } cout << endl; }
Τα παρακάτω παραδείγματα εφαρμόζονται σε container τύπου vector, όμως ο κώδικας της διάτρεξης είναι κοινός για όλους τους containers.
Η διαπέραση από την αρχή προς το τέλος υποστηρίζεται από το σύνολο των iterators.
#include <vector> #include <iostream> #define SIZE 10 using namespace std; int main() { vector<int> ints; for(int i=0; i<10; i++) ints.push_back(SIZE-i); for(vector<int>::const_iterator it=ints.cbegin(); it!=ints.cend(); it++) cout << *it << " "; cout << endl; }
Σημαντική Παρατήρηση: Η συναρτήσεις cbegin και begin επιστρέφουν ένα iterator που δείχνει στο πρώτο στοιχείο του container. Αντίστοιχα, οι συναρτήσεις cend και end επιστρέφουν ένα iterator που δείχνει αμέσως μετά το τελευταίο στοιχείο του container. O iterator διατρέχει τα στοιχεία από την αρχή προς το τέλος του container. Η παρακάτω εικόνα είναι ενδεικτική για τις θέσεις του vector που επιστρέφουν οι συναρτήσεις begin() και end().
Χρησιμοποιούμε τις συναρτήσεις cbegin και cend, διότι οι συγκεκριμένες επιστρέφουν αντικείμενο τύπου const_iterator
και όχι iterator
. Στην περίπτωση που δεν θέλουμε να μεταβάλλουμε τα περιεχόμενα του υφιστάμενου container η χρήση const_iterator είναι ασφαλέστερη διότι ο compiler θα εμφανίσει λάθος σε οποιαδήποτε προσπάθεια μεταβολής.
Η διαπέραση από τον τέλος προς την αρχή υποστηρίζεται μόνο από bidirectional_iterators. Iterators αυτού του τύπου διαθέτουν μόνον οι κλάσεις array, list, vector, dequeue, set και map. Δεν τους υποστηρίζουν οι κλάσεις forward_list, unordered_set και unordered_map.
#include <vector> #include <iostream> #define SIZE 10 using namespace std; int main() { vector<int> ints; for(int i=0; i<10; i++) ints.push_back(SIZE-i); for(vector<int>::const_reverse_iterator it=ints.crbegin(); it!=ints.crend(); it++) cout << *it << " "; cout << endl; }
Παρατήρηση: Η συναρτήσεις crbegin και rbegin επιστρέφουν ένα reverse_iterator που δείχνει στο τελευταίο στοιχείο του container. Αντίστοιχα, οι συναρτήσεις crend και rend επιστρέφουν ένα reverse_iterator που δείχνει αμέσως πριν από το πρώτο στοιχείο του container. Ο reverse_iterator διατρέχει τα στοιχεία από το τέλος προς την αρχή του container.
Ομοίως, χρησιμοποιούμε τις συναρτήσεις crbegin και crend, διότι οι συγκεκριμένες επιστρέφουν const_reverse_iterator
και όχι reverse_iterator
. Στην περίπτωση που δεν θέλουμε να μεταβάλλουμε τα περιεχόμενα του υφιστάμενου container η χρήση const_reverse_iterator είναι ασφαλέστερη για τους λόγους που τεκμηριώθηκαν παραπάνω.
#include <vector> #include <iostream> #define SIZE 10 using namespace std; int main() { vector<int> ints; for(int i=0; i<10; i++) ints.push_back(SIZE-i); for(vector<int>::iterator it=ints.begin(); it!=ints.end(); it++) { *it += 10; // we modify container here cout << *it << " "; } cout << endl; }
#include <vector> #include <iostream> #define SIZE 10 using namespace std; int main() { vector<int> ints; for(int i=0; i<10; i++) ints.push_back(SIZE-i); for(vector<int>::reverse_iterator it=ints.rbegin(); it!=ints.rend(); it++) { *it += 10; // we modify container here cout << *it << " "; } cout << endl; }
Κατά την δήλωση του iterator μπορείτε να χρησιμοποιήσετε τον πριοσδιοριστή auto
αντί για τον πλήρη τύπο του iterator. O compiler έχει την “εξυπνάδα” να εξάγει τον τύπο δεδομένων της μεταβλητής που δηλώνεται μέσω του keyward auto
από τον τύπο δεδομένων που βρίσκεται στα δεξιά του τελεστή =. Στα παραπάνω παραδείγματα, ο τύπος στα δεξιά του τελεστή = προσδιορίζεται από τον επιστρεφόμενο τύπο των συναρτήσεων begin, rbegin κλπ.
Στο προηγούμενο παράδειγμα, η διάτρεξη από το τέλος προς την αρχή με μεταβολή του container θα μπορούσε να γραφεί εναλλακτικά (και συντομότερα) ως εξής:
#include <vector> #include <iostream> #define SIZE 10 using namespace std; int main() { vector<int> ints; for(int i=0; i<10; i++) ints.push_back(SIZE-i); for(auto it=ints.rbegin(); it!=ints.rend(); it++) { *it += 10; // we modify container here cout << *it << " "; } cout << endl; }
Για όλους τους iterators είναι διαθέσιμη η συνάρτηση advance η οποία μετακινεί τον iterator κατά όσες θέσεις προσδιορίζει το δεύτερο όρισμα (μπορεί να πάρει και αρνητικές τιμές για Bidirectional και Random Access iterators). Η συνάρτηση λειτουργεί με χρήση του τελεστή ++
, μετακινώντας τον iterator κατά μία θέση κάθε φορά, όσες φορές απαιτείται.
Εάν θέλουμε να υπολογίσουμε την απόσταση μεταξύ δύο iterators παρέχεται η συνάρτηση distance η οποία υπολογίζει την απόσταση από το πρώτο όρισμα (first) έως το δεύτερο όρισμα (last) μετακινώντας τον δείκτη από first έως last με χρήση του τελεστή ++
. Η συνάρτηση υποθέτει ότι το first βρίσκεται πριν από το last.
Δείτε το παρακάτω παράδειγμα χρήσης των συγκεκριμένων συναρτήσεων
#include <iostream> // std::cout #include <iterator> // std::advance #include <list> // std::list int main () { std::list<int> mylist; for (int i=0; i<10; i++) mylist.push_back (i*10); for(std::list<int>::const_iterator it = mylist.cbegin(); it!=mylist.cend(); ++it) std::cout << *it << " "; std::cout << std::endl; std::list<int>::iterator it = mylist.begin(); std::advance (it,5); std::cout << "The sixth element in mylist is: " << *it << '\n'; std::list<int>::iterator it1 = mylist.begin(); std::advance(it1,2); std::list<int>::iterator it2 = mylist.end(); std::advance(it2,-2); std::cout << "Distance between it1 and it2 is: " << distance(it1, it2) << std::endl; return 0; }
H STL παρέχει τις templated συναρτήσεις begin και end για την εύρεση της αρχικής και της τελικής θέσης οποιουδήποτε container. Οι συναρτήσεις αυτές είναι διαφορετικές από τις συναρτήσεις μέλη begin και end που περιέχει κάθε container. Παράδειγμα χρήσης των συναρτήσεων αυτών δίνεται παρακάτω:
#include <iostream> // std::cout #include <vector> // std::vector, std::begin, std::end int main () { int foo[] = {10,20,30,40,50}; std::vector<int> bar; // iterate foo: inserting into bar for (auto it = std::begin(foo); it!=std::end(foo); ++it) bar.push_back(*it); // iterate bar: print contents: std::cout << "bar contains:"; for (auto it = std::begin(bar); it!=std::end(bar); ++it) std::cout << ' ' << *it; std::cout << '\n'; return 0; }