इटरेटर: STL को जोड़ने वाली कड़ी
इटरेटर की श्रेणियों को समझें, इनपुट इटरेटर से लेकर कंटिग्यूअस इटरेटर तक। इटरेटर अरिथमेटिक, begin/end परिपाटी, और इटरेटर कंटेनर को एल्गोरिदम से कैसे जोड़ते हैं, यह सीखें।
इटरेटर एब्स्ट्रैक्शन
इटरेटर STL के कंटेनर और एल्गोरिदम के बीच की कड़ी (glue) हैं। M कंटेनर के लिए N एल्गोरिदम लिखने (N*M इम्प्लीमेंटेशन) के बजाय, STL एक साझा इटरेटर इंटरफ़ेस तय करता है। हर कंटेनर इटरेटर देता है, और हर एल्गोरिदम इटरेटर रेंज पर काम करता है — इस तरह N+M इम्प्लीमेंटेशन मिलकर साथ काम करते हैं।
इटरेटर एक ऑब्जेक्ट है जो:
1. किसी सीक्वेंस के एक एलिमेंट की ओर इंगित करता है
2. एलिमेंट तक पहुँचने के लिए डीरेफ़रेंस (*it) किया जा सकता है
3. अगले एलिमेंट पर जाने के लिए इन्क्रीमेंट (++it) किया जा सकता है
4. सीक्वेंस का अंत पहचानने के लिए तुलना (it != end) की जा सकती है
अलग-अलग इटरेटर श्रेणियाँ (categories) ऑपरेशन के अलग-अलग सेट सपोर्ट करती हैं, और सबसे कमज़ोर से सबसे मज़बूत तक एक पदानुक्रम बनाती हैं।
इटरेटर की श्रेणियाँ (Iterator Categories)
C++ की इटरेटर श्रेणियाँ एक पदानुक्रम बनाती हैं (हर स्तर में उसके नीचे के सभी स्तरों की क्षमताएँ शामिल होती हैं):
| श्रेणी | ऑपरेशन | उदाहरण कंटेनर |
|---|---|---|
| Input | पढ़ना, एक-पास आगे की ओर (++, *, ==) | istream_iterator |
| Output | लिखना, एक-पास आगे की ओर (++, *=) | ostream_iterator, back_inserter |
| Forward | पढ़ना/लिखना, मल्टी-पास आगे की ओर | forward_list, unordered_set |
| Bidirectional | Forward + पीछे की ओर (--) | list, set, map |
| Random Access | Bidirectional + छलाँगें (+n, -n, [], <) | deque |
| Contiguous (C++17) | Random Access + एलिमेंट मेमोरी में सटे हुए होते हैं | vector, array, string |
एल्गोरिदम बताते हैं कि उन्हें कम से कम (minimum) कौन-सी श्रेणी चाहिए। std::sort को रैंडम एक्सेस इटरेटर चाहिए, इसलिए यह vector के साथ चलता है लेकिन list के साथ नहीं (जो अपना sort() मेंबर फ़ंक्शन देती है)।
begin/end परिपाटी
हर STL कंटेनर begin() और end() देता है — एक हाफ़-ओपन रेंज [begin, end), जिसमें end आख़िरी एलिमेंट के एक आगे की ओर इंगित करता है। फ़्री फ़ंक्शन std::begin() और std::end() कंटेनर, C-ऐरे, और किसी भी ऐसे टाइप के साथ काम करते हैं जो मेंबर .begin() और .end() देता हो।
#include <vector>
#include <list>
#include <algorithm>
#include <iostream>
#include <iterator>
int main() {
std::vector<int> vec = {5, 3, 1, 4, 2};
// Member functions
auto first = vec.begin(); // points to 5
auto last = vec.end(); // points past 2
// Free functions — preferred for generic code
auto first2 = std::begin(vec);
auto last2 = std::end(vec);
// Works with C-arrays too
int arr[] = {10, 20, 30};
std::sort(std::begin(arr), std::end(arr));
// const iterators — read-only access
auto cit = vec.cbegin(); // const_iterator
// *cit = 99; // ERROR: cannot modify through const_iterator
// Reverse iterators — iterate backward
for (auto rit = vec.rbegin(); rit != vec.rend(); ++rit) {
std::cout << *rit << ' '; // 2, 4, 1, 3, 5 (reversed, pre-sort was applied)
}
std::cout << '\n';
}इटरेटर यूटिलिटी: next, prev, advance, distance
हेडर ऐसे यूटिलिटी फ़ंक्शन देता है जो सभी इटरेटर श्रेणियों में काम करते हैं और अपने-आप सबसे कुशल इम्प्लीमेंटेशन चुन लेते हैं।
#include <iterator>
#include <vector>
#include <list>
#include <iostream>
int main() {
std::vector<int> vec = {10, 20, 30, 40, 50};
// std::next — returns iterator n positions forward (default 1)
auto it = std::next(vec.begin(), 2); // points to 30
std::cout << *it << '\n'; // 30
// std::prev — returns iterator n positions backward
auto it2 = std::prev(vec.end()); // points to 50
std::cout << *it2 << '\n'; // 50
// std::advance — modifies iterator in place (no return)
auto it3 = vec.begin();
std::advance(it3, 3); // now points to 40
std::cout << *it3 << '\n'; // 40
// std::distance — number of increments from first to last
auto d = std::distance(vec.begin(), vec.end()); // 5
std::cout << "Distance: " << d << '\n';
// These work with non-random-access iterators too
std::list<int> lst = {1, 2, 3, 4, 5};
auto lit = std::next(lst.begin(), 2); // O(n) for list, O(1) for vector
std::cout << *lit << '\n'; // 3
auto ld = std::distance(lst.begin(), lst.end()); // O(n) for list
std::cout << "List distance: " << ld << '\n'; // 5
}इन्सर्ट इटरेटर और स्ट्रीम इटरेटर
इन्सर्ट इटरेटर एल्गोरिदम को मौजूदा एलिमेंट को ओवरराइट करने के बजाय कंटेनर में इन्सर्ट करने देते हैं। स्ट्रीम इटरेटर I/O स्ट्रीम को इटरेटर इंटरफ़ेस के अनुकूल बनाते हैं।
#include <iterator>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst;
// back_inserter — calls push_back on each assignment
std::copy(src.begin(), src.end(), std::back_inserter(dst));
// dst: {1, 2, 3, 4, 5}
// front_inserter — calls push_front (works with deque, list)
// inserter — calls insert at a given position
// ostream_iterator — write to an output stream
std::copy(dst.begin(), dst.end(),
std::ostream_iterator<int>(std::cout, ", "));
std::cout << '\n'; // 1, 2, 3, 4, 5,
// istream_iterator — read from an input stream
std::istringstream iss("10 20 30 40");
std::vector<int> from_stream(
std::istream_iterator<int>(iss),
std::istream_iterator<int>() // end-of-stream sentinel
);
// from_stream: {10, 20, 30, 40}
}इटरेटर को end() से आगे इन्क्रीमेंट करना या begin() से पीछे डिक्रीमेंट करना अपरिभाषित व्यवहार (undefined behavior) है। end() को डीरेफ़रेंस करने पर भी यही लागू होता है — यह एक सेंटिनल है, वैध एलिमेंट नहीं।
एक आम ग़लती है यह जाँचे बिना it + 1 लिखना कि it पहले से अंत पर या उसके पास है या नहीं:
auto it = vec.end();
++it; // UB — past-the-end
*it; // UB — end() को डीरेफ़रेंस करनाऑपरेशन से पहले हमेशा इटरेटर की वैधता जाँचें। दूरी की जाँच के साथ std::next और std::prev का उपयोग करें, या रेंज-आधारित for लूप इस्तेमाल करें जो सीमाएँ अपने-आप संभाल लेते हैं।
जब आप ऐसे जेनेरिक फ़ंक्शन लिखें जो सीक्वेंस पर काम करते हैं:
1. किसी ख़ास कंटेनर के बजाय इटरेटर के जोड़े (या C++20 में रेंज) स्वीकार करें
2. इटरेटर टाइप के लिए typename या auto का उपयोग करें
3. घटाव की जगह std::distance का उपयोग करें (घटाव के लिए रैंडम एक्सेस ज़रूरी है)
4. +/- अरिथमेटिक की जगह std::next/std::prev का उपयोग करें
5. C++20 में, इटरेटर की आवश्यकताओं को सीमित करने के लिए कॉन्सेप्ट को प्राथमिकता दें:
template<std::forward_iterator Iter>
auto my_algorithm(Iter first, Iter last) { ... }इससे यह सुनिश्चित होता है कि आपका कोड ज़्यादा से ज़्यादा तरह के कंटेनरों के साथ काम करे।
- इटरेटर कंटेनर को एल्गोरिदम से अलग (decouple) करते हैं — यह STL का मूल डिज़ाइन सिद्धांत है
- इटरेटर की छह श्रेणियाँ हैं: input, output, forward, bidirectional, random access, contiguous
- ऐसे जेनेरिक कोड के लिए, जो कंटेनर और C-ऐरे दोनों के साथ चले, फ़्री फ़ंक्शन
std::begin()/std::end()का उपयोग करें std::next,std::prev,std::advance,std::distanceसभी इटरेटर श्रेणियों में काम करते हैं- इन्सर्ट इटरेटर (
back_inserter,front_inserter,inserter) एल्गोरिदम को कंटेनर बढ़ाने देते हैं end()को कभी डीरेफ़रेंस न करें और न उससे आगे इन्क्रीमेंट करें — दोनों अपरिभाषित व्यवहार हैं
क्विज़ — अपना ज्ञान परखें
(15 XP)1. `std::sort` को कौन-सी इटरेटर श्रेणी चाहिए?
2. `std::back_inserter(vec)` क्या बनाता है?
3. `std::list` पर `std::distance(first, last)` की टाइम कॉम्प्लेक्सिटी क्या है?