सामग्री पर जाएं

इटरेटर: 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() देता हो।

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

हेडर ऐसे यूटिलिटी फ़ंक्शन देता है जो सभी इटरेटर श्रेणियों में काम करते हैं और अपने-आप सबसे कुशल इम्प्लीमेंटेशन चुन लेते हैं।

iterator_utils.cpp
#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 स्ट्रीम को इटरेटर इंटरफ़ेस के अनुकूल बनाते हैं।

insert_stream_iterators.cpp
#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)` की टाइम कॉम्प्लेक्सिटी क्या है?