User Tools

Site Tools


cpp:stl:iterators

STL Iterators

Μία κλάση τύπου iterator έχει την δυνατότητα διάτρεξης μιας άλλης κλάσης που αντιπροσωπεύει μία δομή δεδομένων (τη γνωρίσαμε ως container). Η κλάση iterator γνωρίζει την εσωτερική δομή της κλάσης τύπου container και υλοποιεί τον αλγόριθμο για να διαπεράσει το σύνολο των στοιχείων της. Κάθε iterator περιέχει κατ'ελάχιστο ένα δείκτη προς το τρέχον στοιχείο προς διάτρεξη του container στον οποίο αναφέρεται. Όλοι οι iterators ανεξάρτητα από τον τύπο τους υποστηρίζουν τις λειτουργίες:

  • T::iterator it(other_it): copy-constructor
  • it = other_it: copy-assignment
  • iterator destructor
  • ++it ή it++: μετακίνηση κατά μία θέση μέσα στον container και επιστροφή ενός iterator που δείχνει στην τρέχουσα θέση ή στην προηγούμενη θέση του container αντιστοίχως.
  • Υπερφορτώνουν τους τελεστές == και != που υλοποιούν τη σύγκριση ως προς την ισότητα/ανισότητα δύο iterators. Δύο iterators είναι ίσοι εάν δείχνουν στην ίδια θέση του ιδίου container

Οι κατηγορίες (τύποι)των iterator είναι οι παρακάτω. Ένας iterator μπορεί να ανήκει σε περισσότερους από ένα τύπους.

  • Input Iterator: Πρόκειται για έναν iterator από τον οποίο μπορούμε μόνο να διαβάσουμε το τρέχον στοιχείο της δομής στο οποίο δείχνει. Πρόκειται για έναν iterator ανάγνωσης του περιεχόμενου της δομής. Όλοι οι διαθέσιμοι iterators είναι Input Iterators. Παράδειγμα:
input_iterator.cpp
#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;
}
  • Output Iterator: Πρόκειται για έναν iterator μέσω του οποίου μπορούμε να διαβάσουμε και να γράψουμε στο τρέχον στοιχείο του container. Οι iterators που στον τύπο τους περιέχουν το συνθετικό const_ (const_iterator, const_reverse_iterator) δεν ανήκουν στη συγκεκριμένη κατηγορία. Παραδείγματα:
output_iterator.cpp
#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 << " ";
  }
}
  • Bidirectional Iterator: Πρόκειται για iterators που εκτός των πράξεων ++it, it++ υποστηρίζουν και τις πράξεις --it, it--. Bidirectional Iterators είναι όλοι οι iterators με εξαίρεση εκείνούς που ανήκουν σε Unordered Associative Containers και οι iterators του container forward_list (απλά συνδεδεμένη λίστα). Παραδείγματα:
bidirectional_iterator.cpp
#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 κατά ένα.

reverse_iterator.cpp
#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;
}
  • Random Access Iterators: Πρόκειται για iterators που υποστηρίζουν την πράξη της πρόσθεσης και τις αφαίρεσης μεταξύ του iterator και ενός ακεραίου αριθμού (η πράξη είναι ανάλογη της αριθμητικής δεικτών). Οι iterators αυτής της κατηγορίας υποστηρίζονται από τους Sequence Containers array, vector και deque αλλά όχι από τους containers list και forward_list. Παράδειγμα:
random_access_iterator.cpp
#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;
}

Διάτρεξη οποιαδήποτε δομής με χρήση iterator

Τα παρακάτω παραδείγματα εφαρμόζονται σε container τύπου vector, όμως ο κώδικας της διάτρεξης είναι κοινός για όλους τους containers.

Διαπέραση από την αρχή προς το τέλος χωρίς μεταβολή του container

Η διαπέραση από την αρχή προς το τέλος υποστηρίζεται από το σύνολο των iterators.

fw_iterate.cpp
#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 θα εμφανίσει λάθος σε οποιαδήποτε προσπάθεια μεταβολής.

Διαπέραση από το τέλος προς την αρχή χωρίς μεταβολή του container

Η διαπέραση από τον τέλος προς την αρχή υποστηρίζεται μόνο από bidirectional_iterators. Iterators αυτού του τύπου διαθέτουν μόνον οι κλάσεις array, list, vector, dequeue, set και map. Δεν τους υποστηρίζουν οι κλάσεις forward_list, unordered_set και unordered_map.

bw_iterate.cpp
#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 είναι ασφαλέστερη για τους λόγους που τεκμηριώθηκαν παραπάνω.

Διάτρεξη από την αρχή προς το τέλος και μεταβολή του container

fw_iterate_modify.cpp
#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;
}

Διάτρεξη από το τέλος προς την αρχή και μεταβολή του container

bw_iterate_modify.cpp
#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 θα μπορούσε να γραφεί εναλλακτικά (και συντομότερα) ως εξής:

bw_iterate_modify_auto.cpp
#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

Για όλους τους iterators είναι διαθέσιμη η συνάρτηση advance η οποία μετακινεί τον iterator κατά όσες θέσεις προσδιορίζει το δεύτερο όρισμα (μπορεί να πάρει και αρνητικές τιμές για Bidirectional και Random Access iterators). Η συνάρτηση λειτουργεί με χρήση του τελεστή ++, μετακινώντας τον iterator κατά μία θέση κάθε φορά, όσες φορές απαιτείται.

Εάν θέλουμε να υπολογίσουμε την απόσταση μεταξύ δύο iterators παρέχεται η συνάρτηση distance η οποία υπολογίζει την απόσταση από το πρώτο όρισμα (first) έως το δεύτερο όρισμα (last) μετακινώντας τον δείκτη από first έως last με χρήση του τελεστή ++. Η συνάρτηση υποθέτει ότι το first βρίσκεται πριν από το last.

Δείτε το παρακάτω παράδειγμα χρήσης των συγκεκριμένων συναρτήσεων

advance_distance.cpp
#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;
}

Κοινές συναρτήσεις για όλους τους containers για την επιστροφή των θέσεων αρχής και τέλους

H STL παρέχει τις templated συναρτήσεις begin και end για την εύρεση της αρχικής και της τελικής θέσης οποιουδήποτε container. Οι συναρτήσεις αυτές είναι διαφορετικές από τις συναρτήσεις μέλη begin και end που περιέχει κάθε container. Παράδειγμα χρήσης των συναρτήσεων αυτών δίνεται παρακάτω:

iterator_begin_end.cpp
#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;
}
cpp/stl/iterators.txt · Last modified: 2023/05/30 18:48 by gthanos