खोज…
सी Iterators (संकेत)
// This creates an array with 5 values.
const int array[] = { 1, 2, 3, 4, 5 };
#ifdef BEFORE_CPP11
// You can use `sizeof` to determine how many elements are in an array.
const int* first = array;
const int* afterLast = first + sizeof(array) / sizeof(array[0]);
// Then you can iterate over the array by incrementing a pointer until
// it reaches past the end of our array.
for (const int* i = first; i < afterLast; ++i) {
std::cout << *i << std::endl;
}
#else
// With C++11, you can let the STL compute the start and end iterators:
for (auto i = std::begin(array); i != std::end(array); ++i) {
std::cout << *i << std::endl;
}
#endif
यह कोड इस तरह से प्रत्येक पंक्ति पर 5 के माध्यम से संख्या 1 को आउटपुट करेगा:
1
2
3
4
5
इसे तोड़कर
const int array[] = { 1, 2, 3, 4, 5 };
यह रेखा 5 मानों के साथ एक नया पूर्णांक सरणी बनाती है। C सरणियाँ केवल स्मृति के लिए संकेत हैं जहाँ प्रत्येक मान को एक सन्निहित ब्लॉक में एक साथ संग्रहीत किया जाता है।
const int* first = array;
const int* afterLast = first + sizeof(array) / sizeof(array[0]);
ये रेखाएं दो बिंदु बनाती हैं। पहले पॉइंटर को एरे पॉइंटर का मान दिया जाता है, जो एरे में पहले तत्व का पता होता है। C सरणी पर उपयोग किया जाने वाला sizeof
ऑपरेटर बाइट्स में सरणी का आकार देता है। एक तत्व के आकार से विभाजित यह सरणी में तत्वों की संख्या देता है। हम सरणी के बाद ब्लॉक का पता खोजने के लिए इसका उपयोग कर सकते हैं।
for (const int* i = first; i < afterLast; ++i) {
यहां हम एक पॉइंटर बनाते हैं जिसका उपयोग हम एक इटरेटर के रूप में करेंगे। इसे पहले तत्व के पते के साथ आरंभीकृत किया गया है, जिस पर हम चलना चाहते हैं, और हम इसे तब तक जारी रखना चाहेंगे जब तक कि i
afterLast
से कम नहीं हो afterLast
, जिसका अर्थ है कि जब तक i
array
भीतर एक पते की ओर इशारा करता i
।
std::cout << *i << std::endl;
अंत में, लूप के भीतर हम अपने itter के मूल्य को एक्सेस कर सकते हैं जिसे i
इसे डीफ्रॉन्फ्रेंस करके इंगित कर रहा i
। यहाँ dereference ऑपरेटर *
i
में पते पर मान लौटाता है।
अवलोकन
Iterators स्थिति हैं
Iterators तत्वों के एक क्रम पर नेविगेट और संचालित करने का एक साधन हैं और संकेत का एक सामान्यीकृत विस्तार है। वैचारिक रूप से यह याद रखना महत्वपूर्ण है कि पुनरावृत्तियाँ स्थिति हैं, तत्व नहीं। उदाहरण के लिए, निम्नलिखित अनुक्रम लें:
A B C
अनुक्रम में तीन तत्व और चार पद शामिल हैं
+---+---+---+---+
| A | B | C | |
+---+---+---+---+
तत्व एक क्रम के भीतर की चीजें हैं। पद ऐसे स्थान हैं जहां सार्थक संचालन अनुक्रम में हो सकते हैं। उदाहरण के लिए, एक तत्व ए में, पहले या बाद में एक स्थिति में सम्मिलित करता है, एक तत्व में नहीं। यहां तक कि किसी तत्व का विलोपन ( erase(A)
पहले उसकी स्थिति का पता लगाकर किया जाता है, फिर उसे हटा दिया जाता है।
Iterators से मान तक
एक स्थिति से एक मान में बदलने के लिए, एक पुनरावृत्ति को संदर्भित किया जाता है :
auto my_iterator = my_vector.begin(); // position
auto my_value = *my_iterator; // value
एक पुनरावृत्त के बारे में सोच सकते हैं कि यह अनुक्रम में संदर्भित मूल्य के लिए डीरफ्रेंसिंग के रूप में है। यह समझने में विशेष रूप से उपयोगी है कि आपको कभी भी end()
पुनरावृत्त को अनुक्रम में क्यों नहीं करना चाहिए:
+---+---+---+---+
| A | B | C | |
+---+---+---+---+
↑ ↑
| +-- An iterator here has no value. Do not dereference it!
+-------------- An iterator here dereferences to the value A.
C ++ मानक पुस्तकालय में पाए जाने वाले सभी अनुक्रमों और कंटेनरों में, begin()
पहली स्थिति में एक पुनरावृत्तिकर्ता को लौटाएगा, और end()
अंतिम स्थान (पिछले स्थिति नहीं !) के लिए एक पुनरावृत्तिकर्ता को लौटाएगा। नतीजतन, एल्गोरिदम में इन पुनरावृत्तियों के नाम अक्सर first
और last
लेबल होते हैं:
+---+---+---+---+
| A | B | C | |
+---+---+---+---+
↑ ↑
| |
+- first +- last
किसी भी अनुक्रम के लिए एक पुनरावृत्ति प्राप्त करना भी संभव है, क्योंकि यहां तक कि एक खाली अनुक्रम में कम से कम एक स्थिति होती है:
+---+
| |
+---+
एक खाली क्रम में, begin()
और end()
एक ही स्थिति होगी, और न ही डीरफेर किया जा सकता है:
+---+
| |
+---+
↑
|
+- empty_sequence.begin()
|
+- empty_sequence.end()
पुनरावृत्तियों का वैकल्पिक दृश्य यह है कि वे तत्वों के बीच के पदों को चिह्नित करते हैं:
+---+---+---+
| A | B | C |
+---+---+---+
↑ ^ ^ ↑
| |
+- first +- last
और एक इटरेटर को डीफेररिंग करने से इटरेटर के बाद आने वाले तत्व का संदर्भ मिलता है। कुछ परिस्थितियां जहां यह दृश्य विशेष रूप से उपयोगी है:
-
insert
कार्य तत्वों को पुनरावृत्ति द्वारा इंगित स्थिति में सम्मिलित करेगा, -
erase
ऑपरेशन उसी स्थान के अनुसार एक पुनरावृत्तिकर्ता को लौटाएंगे, जिसमें कोई पास हुआ था, - एक इट्रेटर और उसके संबंधित रिवर्स इटरेटर तत्वों के बीच एक ही स्थिति में स्थित हैं
अमान्य इटरेटर
एक पुनरावृत्त अमान्य हो जाता है अगर (एक ऑपरेशन के दौरान), इसकी स्थिति अब अनुक्रम का हिस्सा नहीं है। एक अमान्य पुनरावृत्ति को तब तक स्थगित नहीं किया जा सकता है जब तक कि उसे किसी मान्य स्थिति में पुन: असाइन नहीं किया गया हो। उदाहरण के लिए:
std::vector<int>::iterator first;
{
std::vector<int> foo;
first = foo.begin(); // first is now valid
} // foo falls out of scope and is destroyed
// At this point first is now invalid
C ++ मानक पुस्तकालय में कई एल्गोरिदम और अनुक्रम सदस्य कार्यों में नियम होते हैं जब पुनरावृत्तियों को अमान्य कर दिया जाता है। प्रत्येक एल्गोरिथ्म अलग-अलग तरीके से व्यवहार करते हैं (और अमान्य) पुनरावृत्तियों।
Iterators के साथ नेविगेट करना
जैसा कि हम जानते हैं कि पुनरावृत्तियाँ दृश्यों को नेविगेट करने के लिए होती हैं। ऐसा करने के लिए कि एक पुनरावृत्त को पूरे क्रम में अपनी स्थिति को स्थानांतरित करना होगा। इटरेटर अनुक्रम में आगे बढ़ सकते हैं और कुछ पीछे की ओर आगे बढ़ सकते हैं:
auto first = my_vector.begin();
++first; // advance the iterator 1 position
std::advance(first, 1); // advance the iterator 1 position
first = std::next(first); // returns iterator to the next element
std::advance(first, -1); // advance the iterator 1 position backwards
first = std::next(first, 20); // returns iterator to the element 20 position forward
first = std::prev(first, 5); // returns iterator to the element 5 position backward
auto dist = std::distance(my_vector.begin(), first); // returns distance between two iterators.
ध्यान दें, std का दूसरा तर्क :: दूरी पहले वाले (या, दूसरे शब्दों में first
second
तुलना में कम या बराबर) होनी चाहिए।
भले ही आप पुनरावृत्तियों के साथ अंकगणितीय ऑपरेटर कर सकते हैं, लेकिन सभी प्रकार के पुनरावृत्तियों के लिए सभी संचालन परिभाषित नहीं हैं। a = b + 3;
रैंडम एक्सेस Iterators के लिए काम करेगा, लेकिन फॉरवर्ड या द्विदिश Iterators के लिए काम नहीं करेगा, जो अभी भी b = a; ++b; ++b; ++b;
जैसी किसी चीज के साथ 3 स्थिति से उन्नत हो सकता b = a; ++b; ++b; ++b;
। इसलिए यह सुनिश्चित करने के लिए विशेष कार्यों का उपयोग करने की सिफारिश की जाती है कि आप यह सुनिश्चित नहीं कर रहे हैं कि इटरेटर प्रकार क्या है (उदाहरण के लिए, टेम्पलेट को स्वीकार करने वाले फ़ंक्शन में)।
Iterator अवधारणाओं
C ++ मानक कई अलग-अलग पुनरावृत्त अवधारणाओं का वर्णन करता है। इन्हें इस बात के अनुसार वर्गीकृत किया गया है कि वे उन दृश्यों के साथ कैसे व्यवहार करते हैं जिन्हें वे संदर्भित करते हैं। यदि आप अवधारणा को एक इट्रेटर मॉडल (जैसे व्यवहार करता है) जानते हैं, तो आप उस पुनरावृत्ति के व्यवहार के बारे में आश्वस्त हो सकते हैं, भले ही वह जिस क्रम से हो । उन्हें अक्सर सबसे कम प्रतिबंधात्मक से क्रम में वर्णित किया जाता है (क्योंकि अगले पुनरावृत्ति अवधारणा इसके पूर्ववर्ती की तुलना में एक कदम बेहतर है):
- इनपुट Iterators: केवल एक बार स्थिति के अनुसार dereferenced जा सकता है। केवल अग्रिम कर सकते हैं, और एक समय में केवल एक ही स्थिति।
- फॉरवर्ड इटरेटर्स: एक इनपुट इटेरेटर जिसे किसी भी समय डिरेल किया जा सकता है।
- द्विदिश Iterators: एक आगे चलने वाला जो एक समय में एक स्थिति को पीछे की ओर भी आगे बढ़ा सकता है।
- रैंडम एक्सेस Iterators: एक द्विदिश पुनरावृत्ति जो एक समय में किसी भी संख्या में आगे या पीछे की ओर आगे बढ़ सकता है।
- सन्निहित Iterators (C ++ 17 के बाद से): एक यादृच्छिक अभिगमकर्ता जो यह गारंटी देता है कि अंतर्निहित डेटा स्मृति में सन्निहित है।
एल्गोरिदम उनके द्वारा दिए गए पुनरावृत्तियों द्वारा प्रतिरूपित अवधारणा के आधार पर भिन्न हो सकते हैं। उदाहरण के लिए, हालांकि random_shuffle
को आगे random_shuffle
लिए कार्यान्वित किया जा सकता है, एक अधिक कुशल संस्करण जिसके लिए यादृच्छिक random_shuffle
आवश्यकता हो सकती है।
इटरेटर लक्षण
Iterator लक्षण पुनरावृत्तियों के गुणों को एक समान इंटरफ़ेस प्रदान करते हैं। वे आपको मान, अंतर, सूचक, संदर्भ प्रकार और पुनरावृत्ति श्रेणी भी प्राप्त करने की अनुमति देते हैं:
template<class Iter>
Iter find(Iter first, Iter last, typename std::iterator_traits<Iter>::value_type val) {
while (first != last) {
if (*first == val)
return first;
++first;
}
return last;
}
एल्गोरिदम के विशेषज्ञ के लिए पुनरावृत्ति की श्रेणी का उपयोग किया जा सकता है:
template<class BidirIt>
void test(BidirIt a, std::bidirectional_iterator_tag) {
std::cout << "Bidirectional iterator is used" << std::endl;
}
template<class ForwIt>
void test(ForwIt a, std::forward_iterator_tag) {
std::cout << "Forward iterator is used" << std::endl;
}
template<class Iter>
void test(Iter a) {
test(a, typename std::iterator_traits<Iter>::iterator_category());
}
पुनरावृत्तियों की श्रेणियाँ मूल रूप से पुनरावृत्तियों की अवधारणाएं हैं, सिवाय इसके कि कंटेगोरस इटरेटर्स का अपना टैग नहीं है, क्योंकि यह कोड तोड़ने के लिए पाया गया था।
रिवर्स इटरेटर
यदि हम किसी सूची या वेक्टर के माध्यम से पीछे की ओर चलना चाहते हैं तो हम एक reverse_iterator
उपयोग कर सकते हैं। एक रिवर्स इट्रीमीटर एक द्विदिश, या रैंडम एक्सेस इटरेटर से बनाया जाता है, जिसे वह एक सदस्य के रूप में रखता है जिसे base()
माध्यम से एक्सेस किया जा सकता है।
पुनरावृत्ति करने के लिए उपयोग पीछे की ओर rbegin()
और rend()
संग्रह के अंत के लिए iterators के रूप में, और क्रमशः संग्रह की शुरुआत।
उदाहरण के लिए, पीछे की ओर उपयोग करने के लिए:
std::vector<int> v{1, 2, 3, 4, 5};
for (std::vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it)
{
cout << *it;
} // prints 54321
एक रिवर्स इटरेटर को base()
सदस्य फ़ंक्शन के माध्यम से फॉरवर्ड इटरेटर में परिवर्तित किया जा सकता है। संबंध यह है कि रिवर्स इटरेटर base()
पिछले एक तत्व को संदर्भित base()
इट्रेटर:
std::vector<int>::reverse_iterator r = v.rbegin();
std::vector<int>::iterator i = r.base();
assert(&*r == &*(i-1)); // always true if r, (i-1) are dereferenceable
// and are not proxy iterators
+---+---+---+---+---+---+---+
| | 1 | 2 | 3 | 4 | 5 | |
+---+---+---+---+---+---+---+
↑ ↑ ↑ ↑
| | | |
rend() | rbegin() end()
| rbegin().base()
begin()
rend().base()
विज़ुअलाइज़ेशन में जहां पुनरावृत्तियाँ तत्वों के बीच स्थिति को चिह्नित करती हैं, संबंध सरल है:
+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 |
+---+---+---+---+---+
↑ ↑
| |
| end()
| rbegin()
begin() rbegin().base()
rend()
rend().base()
वेक्टर Iterator
अनुक्रम कंटेनर में पहले तत्व के लिए एक iterator
begin
है।
end
के पहले तत्व के लिए एक iterator
देता है।
वेक्टर वस्तु है const
, दोनों begin
और end
एक वापसी const_iterator
। यदि आप चाहते हैं एक const_iterator
लौटा दी है, भले ही अपने वेक्टर नहीं है const
, तो आप उपयोग कर सकते हैं cbegin
और cend
।
उदाहरण:
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = { 1, 2, 3, 4, 5 }; //intialize vector using an initializer_list
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
आउटपुट:
1 2 3 4 5
मैप इटरेटर
कंटेनर में पहले तत्व के लिए एक पुनरावृत्ति।
यदि कोई मानचित्र ऑब्जेक्ट const_iterator
योग्य है, तो फ़ंक्शन एक const_iterator
देता है। अन्यथा, यह एक iterator
देता iterator
।
// Create a map and insert some values
std::map<char,int> mymap;
mymap['b'] = 100;
mymap['a'] = 200;
mymap['c'] = 300;
// Iterate over all tuples
for (std::map<char,int>::iterator it = mymap.begin(); it != mymap.end(); ++it)
std::cout << it->first << " => " << it->second << '\n';
आउटपुट:
a => 200
बी => १००
c => 300
स्ट्रीम इटरेटर
स्ट्रीम पुनरावृत्तियों तब उपयोगी होती हैं जब हमें किसी कंटेनर से अनुक्रम या मुद्रित स्वरूपित डेटा पढ़ने की आवश्यकता होती है:
// Data stream. Any number of various whitespace characters will be OK.
std::istringstream istr("1\t 2 3 4");
std::vector<int> v;
// Constructing stream iterators and copying data from stream into vector.
std::copy(
// Iterator which will read stream data as integers.
std::istream_iterator<int>(istr),
// Default constructor produces end-of-stream iterator.
std::istream_iterator<int>(),
std::back_inserter(v));
// Print vector contents.
std::copy(v.begin(), v.end(),
//Will print values to standard output as integers delimeted by " -- ".
std::ostream_iterator<int>(std::cout, " -- "));
उदाहरण कार्यक्रम 1 -- 2 -- 3 -- 4 --
मानक आउटपुट पर प्रिंट करेगा।
अपना स्वयं का जेनरेटर समर्थित पुनरावृत्त लिखें
अन्य भाषाओं में एक सामान्य पैटर्न में एक फ़ंक्शन होता है जो ऑब्जेक्ट्स की "स्ट्रीम" का उत्पादन करता है, और इसके ऊपर लूप-कोड का उपयोग करने में सक्षम होता है।
हम इसे C ++ में मॉडल कर सकते हैं
template<class T>
struct generator_iterator {
using difference_type=std::ptrdiff_t;
using value_type=T;
using pointer=T*;
using reference=T;
using iterator_category=std::input_iterator_tag;
std::optional<T> state;
std::function< std::optional<T>() > operation;
// we store the current element in "state" if we have one:
T operator*() const {
return *state;
}
// to advance, we invoke our operation. If it returns a nullopt
// we have reached the end:
generator_iterator& operator++() {
state = operation();
return *this;
}
generator_iterator operator++(int) {
auto r = *this;
++(*this);
return r;
}
// generator iterators are only equal if they are both in the "end" state:
friend bool operator==( generator_iterator const& lhs, generator_iterator const& rhs ) {
if (!lhs.state && !rhs.state) return true;
return false;
}
friend bool operator!=( generator_iterator const& lhs, generator_iterator const& rhs ) {
return !(lhs==rhs);
}
// We implicitly construct from a std::function with the right signature:
generator_iterator( std::function< std::optional<T>() > f ):operation(std::move(f))
{
if (operation)
state = operation();
}
// default all special member functions:
generator_iterator( generator_iterator && ) =default;
generator_iterator( generator_iterator const& ) =default;
generator_iterator& operator=( generator_iterator && ) =default;
generator_iterator& operator=( generator_iterator const& ) =default;
generator_iterator() =default;
};
हम उत्पन्न तत्व को जल्दी स्टोर करते हैं ताकि हम पहले से ही अंत में आसानी से पता लगा सकें।
चूंकि एंड जनरेटर जनरेटर के कार्य का उपयोग कभी नहीं किया जाता है, हम केवल एक बार std::function
कॉपी करके जनरेटर पुनरावृत्तियों की एक श्रृंखला बना सकते हैं। एक डिफ़ॉल्ट निर्मित जनरेटर इटरेटर स्वयं के बराबर, और अन्य सभी एंड-जनरेटर-पुनरावृत्तियों की तुलना करता है।