This is an old revision of the document!
H Standard Template Libray (STL) είναι βιβλιοθήκη της C++ που αποτελεί αναπόσπαστο τμήμα της stardard βιβλιοθήκης της γλώσσας. Αποτελείται από κλάσεις για προσωρινή αποθήκευση πληροφορίας σε ένα πρόγραμμα που ονομάζονται containers, κλάσεις για διάτρεξη των containers (ονομάζονται iterators) και αλγορίθμους. Οι αλγόριθμοι είναι συναρτήσεις που κατά κανόνα λειτουργούν με την βοήθεια των iterators πάνω στους διαθέσιμους containers.
Βασικό χαρακτηριστικό της STL είναι ότι οι κλάσεις (containers και iterators) και οι συναρτήσεις των αλγορίθμων είναι γενικευμένες, ώστε να μπορούν να εφαρμοστούν με ασφάλεια σε οποιονδήποτε τύπο δεδομένων. Για να το επιτύχουν αυτό, χρησιμοποιούν templates.
Στο παρακάτω διάγραμμα περιέχονται το σύνολο των containers που παρέχει η βιβλιοθήκη STL.
Οι containers της STL διακρίνονται στις εξής κατηγορίες:
Μία κλάση τύπου iterator έχει την δυνατότητα διάτρεξης μιας άλλης κλάσης που αντιπροσωπεύει μία δομή δεδομένων (container). O iterator συνήθως γνωρίζει την εσωτερική δομή της κλάσης και πως να μετακινηθεί ανάμεσα στα στοιχεία της. Ο iterator περιέχει κατ'ελάχιστο ένα δείκτη προς το τρέχον στοιχείο προς διάτρεξη του container. Όλοι οι iterators ανεξάρτητα από τον τύπο τους υποστηρίζουν τις λειτουργίες:
T::iterator it(other_it): copy-constructorit = other_it: copy-assignment++it ή it++: μετακίνηση κατά μία θέση μέσα στον container και επιστροφή ενός iterator που δείχνει στην τρέχουσα θέση ή στην προηγούμενη θέση του container αντιστοίχως.Οι κατηγορίες των iterator είναι οι εξής:
for(vector<int>::const_iterator it=ints.cbegin(); it!=ints.cend(); it++) cout << *it << " "; cout << endl;
for(vector<int>::iterator it=ints.begin(); it!=ints.end(); it++) { *it += 10; // we modify container here cout << *it << " "; } 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 (απλά συνδεδεμένη λίστα). Παραδείγματα:for(vector<int>::iterator it=ints.cbegin(); it!=ints.cend(); it++) { cout << *it << " "; } cout << endl; vector<int>::iterator it = ints.cend(); // starting from the end of the container while(true) { --it1; // move to the previous element cout << *it1 << " "; if(it1 == ints.cbegin()) // stop when we reach the first element break; } cout << endl;
vector<int>::iterator it = ints.cend(); // starting from the end of the container while(true) { it -= 2; // move back by 2 elements cout << *it1 << " "; if(it1 <= ints.cbegin()) // stop when we reach or we have passed the first element break; } cout << endl;
Για όλους τους iterators είναι διαθέσιμη η συνάρτηση advance η οποία μετακινεί τον iterator κατά όσες θέσεις προσδιορίζει το δεύτερο όρισμα (μπορεί να πάρει και αρνητικές τιμές για Bidirectional και Random Access iterators). Η συνάρτηση λειτουργεί με χρήση του τελεστή ++, μετακινώντας τον iterator κατά μία θέση κάθε φορά, όσες φορές απαιτείται.
Εάν θέλουμε να υπολογίσουμε την απόσταση μεταξύ δύο iterators παρέχεται η συνάρτηση distance η οποία υπολογίζει την απόσταση από το πρώτο όρισμα (first) έως το δεύτερο όρισμα (last) μετακινώντας τον δείκτη από first έως last με χρήση του τελεστή ++. Η συνάρτηση υποθέτει ότι το first βρίσκεται πριν από το last.
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; }
Τα παρακάτω παραδείγματα εφαρμόζονται σε container τύπου vector, όμως ο κώδικας της διάτρεξης είναι κοινός για όλους τους containers.
#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.
Χρησιμοποιούμε τις συναρτήσεις cbegin και cend, διότι οι συγκεκριμένες επιστρέφουν αντικείμενο τύπου const_iterator και όχι iterator. Στην περίπτωση που δεν θέλουμε να μεταβάλλουμε τα περιεχόμενα του υφιστάμενου container η χρήση const_iterator είναι ασφαλέστερη διότι ο compiler θα εμφανίσει λάθος σε οποιαδήποτε προσπάθεια μεταβολής.
#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. O 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; }
TODO