सरणी सूचकांकों के बजाय पुनरावृत्तियों का उपयोग क्यों करें?


239

कोड की निम्नलिखित दो पंक्तियाँ लें:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

और इस:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

मुझे बताया गया है कि दूसरा तरीका पसंद किया जाता है। आखिर ऐसा क्यों है?


72
दूसरा तरीका पसंद किया जाता है कि आप किसमें बदलाव some_iterator++कर रहे हैं ++some_iterator। पोस्ट-इन्क्रीमेंट एक अनावश्यक अस्थायी पुनरावृत्ति बनाता है।
जेसन

6
आपको end()घोषणा खंड में भी लाना चाहिए ।
ऑर्बिट में लाइटनेस दौड़

5
@ टोमलाक: एक अक्षम व्यक्ति के साथ C ++ कार्यान्वयन का उपयोग करने वाले किसी व्यक्ति के vector::endपास इस बात की चिंता करने के लिए शायद बदतर मुद्दे हैं कि यह छोरों से बाहर फहराया गया है या नहीं। व्यक्तिगत रूप से मैं स्पष्टता पसंद करता हूं - अगर यह findसमाप्ति की स्थिति में कॉल था, तो मुझे चिंता होगी।
स्टीव जेसप

13
@Tomalak: यह कोड मैला नहीं है (ठीक है, पोस्ट-इन्क्रीमेंट हो सकता है), यह संक्षिप्त और स्पष्ट है, जहां तक ​​C ++ iterators सुगमता की अनुमति देते हैं। अधिक चर जोड़ना समयपूर्व अनुकूलन के लिए संज्ञानात्मक प्रयास जोड़ता है। वह टेढ़ा है।
स्टीव जेसप

7
@Tomalak: यदि यह अड़चन नहीं है तो यह समय से पहले है। आपका दूसरा बिंदु मेरे लिए बेतुका लगता है, क्योंकि सही तुलना it != vec.end()और it != endइसके बीच नहीं है , (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)और (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)। मुझे पात्रों को गिनने की आवश्यकता नहीं है। हर तरह से एक दूसरे को पसंद करते हैं, लेकिन आपकी पसंद के साथ अन्य लोगों की असहमति "सुस्ती" नहीं है, यह कम चर के साथ सरल कोड के लिए एक प्राथमिकता है और इस तरह इसे पढ़ने के दौरान कम सोचने के लिए।
स्टीव जेसप

जवाबों:


210

पहला फ़ॉर्म तभी कुशल है जब वेक्टर.साइज़ () एक तेज़ ऑपरेशन है। यह वैक्टर के लिए सच है, लेकिन सूचियों के लिए नहीं, उदाहरण के लिए। इसके अलावा, आप लूप के शरीर के भीतर क्या करने की योजना बना रहे हैं? यदि आप तत्वों को एक्सेस करने की योजना बनाते हैं

T elem = some_vector[i];

तब आप यह अनुमान लगा रहे हैं कि कंटेनर ने operator[](std::size_t)परिभाषित किया है। फिर से, यह वेक्टर के लिए सच है लेकिन अन्य कंटेनरों के लिए नहीं।

पुनरावृत्तियों का उपयोग आपको कंटेनर स्वतंत्रता के करीब लाता है । आप यादृच्छिक-अभिगम क्षमता या तेज़ size()संचालन के बारे में धारणा नहीं बना रहे हैं , केवल यह कि कंटेनर में पुनरावृत्त क्षमताएं हैं।

आप मानक एल्गोरिदम का उपयोग करके अपने कोड को और बढ़ा सकते हैं। आप इसे हासिल करने की कोशिश कर रहे हैं, इसके आधार पर, आप उपयोग करने के लिए चुनाव कर सकते हैं std::for_each(), std::transform()और इसी तरह। एक स्पष्ट लूप के बजाय एक मानक एल्गोरिथ्म का उपयोग करके आप पहिया का फिर से आविष्कार करने से बच रहे हैं। आपका कोड अधिक कुशल होने की संभावना है (सही एल्गोरिदम चुना गया है), सही और पुन: प्रयोज्य।


8
इसके अलावा आप यह भूल गए कि पुनरावृत्तियां विफल-तेज होने जैसी चीजें कर सकती हैं, ताकि यदि आप जिस संरचना तक पहुंच रहे हैं, उसका समवर्ती संशोधन हो, तो आपको इसके बारे में पता चल जाएगा। तुम सिर्फ एक पूर्णांक के साथ ऐसा नहीं कर सकते।
मार्सिन

4
यह मुझे भ्रमित करता है: "यह वैक्टर के लिए सच है, लेकिन सूचियों के लिए नहीं, उदाहरण के लिए।" क्यों? मस्तिष्क वाला कोई भी size_tसदस्य चर का हिसाब रखेगा size()
GManNickG

19
@GMan - लगभग सभी कार्यान्वयन में, आकार () केवल वैक्टर के लिए सूचियों के लिए तेज़ है। मानक के अगले संस्करण के लिए इसे सही होना आवश्यक है। वास्तविक समस्या स्थिति द्वारा प्रतिधारण की सुस्ती है।
डैनियल ईयरविकर

8
@GMan: सूची के आकार को संग्रहीत करने के लिए O (1) के बजाय O (n) होने के लिए सूची को स्लाइस करना और splicing करना आवश्यक है।

5
C ++ 0x में, size()सदस्य फ़ंक्शन को उन सभी कंटेनरों के लिए निरंतर समय की जटिलता की आवश्यकता होगी जो इसे समर्थन करते हैं std::list
जेम्स मैकनेलिस

54

यह आधुनिक C ++ इंडोक्रिटेशन प्रक्रिया का हिस्सा है। Iterators अधिकांश कंटेनरों को पुन: व्यवस्थित करने का एकमात्र तरीका है, इसलिए आप इसे वैक्टर के साथ भी उपयोग करते हैं ताकि आप अपने आप को उचित मानसिकता में ला सकें। गंभीरता से, यही एकमात्र कारण है कि मैं इसे करता हूं - मुझे नहीं लगता कि मैंने कभी एक वेक्टर को एक अलग तरह के कंटेनर के साथ बदल दिया है।


वाह, यह तीन सप्ताह के बाद भी कम हो रहा है। मुझे लगता है कि यह थोड़ा जीभ-गाल होने का भुगतान नहीं करता है।

मुझे लगता है कि सरणी सूचकांक अधिक पठनीय है। यह अन्य भाषाओं में प्रयुक्त वाक्यविन्यास से मेल खाता है, और पुराने जमाने के सी सरणियों के लिए प्रयुक्त वाक्यविन्यास। यह भी कम क्रिया है। यदि आपका कंपाइलर कोई अच्छा है, तो दक्षता एक वॉश होनी चाहिए, और शायद ही कोई मामले हैं जहां यह किसी भी तरह से मायने रखता है।

फिर भी, मैं अभी भी स्वयं को पुनरावृत्तियों का उपयोग करते हुए अक्सर वैक्टर के साथ पाता हूं। मेरा मानना ​​है कि इटरेटर एक महत्वपूर्ण अवधारणा है, इसलिए जब भी मैं कर सकता हूं, मैं इसे बढ़ावा देता हूं।


1
C ++ पुनरावृत्तिकर्ता भी वैचारिक रूप से बुरी तरह टूट जाते हैं। वैक्टर के लिए, मैं बस पकड़ गया क्योंकि अंत सूचक एक्यूट एंड + 1 (!) है। धाराओं के लिए इट्रेटर मॉडल सिर्फ असली है - एक काल्पनिक टोकन जो मौजूद नहीं है। इसी तरह लिंक्ड लिस्ट के लिए। प्रतिमान केवल सरणियों के लिए समझ में आता है, और फिर बहुत कुछ नहीं। मुझे सिर्फ एक ही नहीं, दो
इट्रेटर

5
@aberglas वे बिल्कुल टूटी हुई नहीं हैं, आप सिर्फ उनके लिए उपयोग नहीं कर रहे हैं, यही वजह है कि जब आप उनके पास नहीं हैं तब भी मैं उनका उपयोग करने की वकालत करता हूं! हाफ-ओपन रेंज एक सामान्य अवधारणा है, और प्रहरी जो सीधे पहुंच के लिए कभी नहीं होते हैं वे प्रोग्रामिंग के रूप में पुराने हैं।
मार्क रंसोम

4
स्ट्रीम पुनरावृत्तियों पर एक नज़र डालें और इस बारे में सोचें कि पैटर्न फिट करने के लिए क्या == विकृत किया गया है, और फिर मुझे बताएं कि पुनरावृत्तियां टूटी नहीं हैं! या जुड़ी हुई सूचियों के लिए। यहां तक ​​कि सरणियों के लिए, पिछले एक को निर्दिष्ट करने के लिए एक टूटी हुई सी शैली का विचार है - सूचक कभी नहीं। उन्हें जावा या सी # या किसी अन्य भाषा के पुनरावृत्तियों की तरह होना चाहिए, जिसमें एक पुनरावृत्ति आवश्यक (दो वस्तुओं के बजाय) और एक सरल परीक्षण है।
Tuntable

53

क्योंकि आप अपने कोड को some_vector सूची के विशेष कार्यान्वयन के लिए नहीं बांध रहे हैं। यदि आप सरणी सूचकांकों का उपयोग करते हैं, तो इसे सरणी के कुछ रूप होने चाहिए; यदि आप पुनरावृत्तियों का उपयोग करते हैं, तो आप किसी भी सूची के कार्यान्वयन पर उस कोड का उपयोग कर सकते हैं।


23
Std :: सूची इंटरफ़ेस आशय परिचालक ऑपरेटर [] (size_t n) की पेशकश नहीं करता है क्योंकि यह O (n) होगा।
एमएसलटर्स

33

कल्पना कीजिए कि some_vector एक लिंक्ड-सूची के साथ लागू किया गया है। तब i-th जगह में एक आइटम का अनुरोध करने के लिए मुझे नोड्स की सूची को पार करने के लिए i संचालन की आवश्यकता होती है। अब, यदि आप इट्रेटर का उपयोग करते हैं, आम तौर पर बोल रहे हैं, तो यह यथासंभव सर्वोत्तम प्रयास करने का प्रयास करेगा (लिंक की गई सूची के मामले में, यह वर्तमान नोड के लिए एक संकेतक बनाए रखेगा और प्रत्येक पुनरावृत्ति में इसे आगे बढ़ाएगा, बस एक की आवश्यकता होगी एकल ऑपरेशन)।

तो यह दो चीजें प्रदान करता है:

  • उपयोग का अमूर्त: आप बस कुछ तत्वों को पुनरावृत्त करना चाहते हैं, आप इसे कैसे करना है इसकी परवाह नहीं करते हैं
  • प्रदर्शन

1
"यह वर्तमान नोड के लिए एक संकेतक बनाए रखेगा और इसे आगे बढ़ाएगा [दक्षता के बारे में अच्छा सामान" - हाँ, मुझे नहीं लगता कि लोगों को पुनरावृत्तियों की अवधारणा को समझने में परेशानी क्यों है। वे वैचारिक रूप से सिर्फ संकेतकर्ताओं के एक सुपरसेट हैं। क्यों आप बार-बार किसी पॉइंटर को कैश कर सकते हैं, तो कुछ तत्वों की ऑफसेट की गणना बार-बार करें? ठीक है, यह है कि पुनरावृत्तियों भी करते हैं।
अंडरस्कोर_ड

27

मैं यहाँ शैतानों का पैरोकार बनने जा रहा हूँ, और पुनरावृत्तियों की सिफारिश नहीं कर रहा हूँ। मुख्य कारण यह है कि, मैंने डेस्कटॉप एप्लिकेशन डेवलपमेंट से लेकर गेम डेवलपमेंट तक सभी सोर्स कोड काम किए हैं और न ही मुझे पुनरावृत्तियों का उपयोग करने की आवश्यकता है। हर समय उनकी आवश्यकता नहीं होती है और दूसरी बात यह है कि छिपी हुई धारणाएं और कोड मेस और डिबगिंग बुरे सपने जो आपको पुनरावृत्तियों के साथ मिलते हैं, उन्हें एक प्रमुख उदाहरण बनाते हैं जो इसे किसी भी अनुप्रयोगों में उपयोग करने के लिए नहीं है जो गति की आवश्यकता होती है।

यहां तक ​​कि एक मुख्य रुख बिंदु से वे एक गड़बड़ कर रहे हैं। इसकी वजह उनकी नहीं बल्कि उस सीन के पीछे होने वाली तमाम एलियासिंग की वजह से है। मुझे कैसे पता चलेगा कि आपने अपने स्वयं के वर्चुअल वेक्टर या सरणी सूची को लागू नहीं किया है जो मानकों के लिए पूरी तरह से कुछ करता है। क्या मुझे पता है कि वर्तमान में रनटाइम के दौरान कौन सा प्रकार है? क्या आपने एक ऑपरेटर को अधिभारित किया है जो मेरे पास आपके सभी स्रोत कोड की जांच करने का समय नहीं था। नरक क्या मुझे भी पता है कि एसटीएल आपके किस संस्करण का उपयोग कर रहा है?

अगली समस्या जो आपको पुनरावृत्तियों के साथ मिली है वह है टपका हुआ अमूर्त, हालांकि कई वेब साइटें हैं जो उनके साथ इस पर विस्तार से चर्चा करती हैं।

क्षमा करें, मैंने अभी तक पुनरावृत्तियों में कोई बिंदु नहीं देखा है। यदि वे सूची या वेक्टर को आपसे दूर करते हैं, जब वास्तव में आपको पहले से ही पता होना चाहिए कि वेक्टर क्या है या आपके साथ काम करने की सूची यदि आप नहीं करते हैं तो आपका भविष्य में कुछ महान डिबगिंग सत्रों के लिए खुद को स्थापित करने जा रहा है।


23

यदि आप इस पर पुनरावृत्ति कर रहे हैं तो आप वेक्टर में आइटम जोड़ने / हटाने जा रहे हैं, तो आप एक पुनरावृत्ति का उपयोग करना चाह सकते हैं।

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

यदि आप सूचकांकों का उपयोग कर रहे थे तो आपको सम्मिलन और विलोपन को संभालने के लिए सरणी में आइटम को ऊपर / नीचे फेरबदल करना होगा।


3
यदि आप कंटेनर के बीच में तत्वों को सम्मिलित करना चाहते हैं तो शायद एक वेक्टर शुरू करने के लिए एक अच्छा कंटेनर विकल्प नहीं है। बेशक, हम वापस आ गए हैं कि पुनरावृत्तियां शांत क्यों हैं; किसी सूची में जाना तुच्छ है।
विल्हेमटेल

सभी तत्वों पर Iterating एक std::listकी तुलना में बहुत महंगा है std::vector, हालांकि, यदि आप एक के बजाय एक लिंक्ड-सूची का उपयोग करने की सिफारिश कर रहे हैं std::vector। पृष्ठ 43 देखें: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf मेरे अनुभव में, मैंने पाया std::vectorहै std::listकि अगर मैं इसके बारे में खोज कर रहा हूं और मनमाने पदों पर तत्वों को हटा रहा हूं, तो भी मैं इससे कहीं ज्यादा तेज हूं।
डेविड स्टोन

संकेत स्थिर हैं, इसलिए मैं यह नहीं देखता कि सम्मिलन और विलोपन के लिए अतिरिक्त फेरबदल की क्या आवश्यकता है।
मुसीफिल

... और एक लिंक्ड सूची के साथ - जो कि यहां उपयोग में होना चाहिए - आपका लूप स्टेटमेंट वह होगा for (node = list->head; node != NULL; node = node->next)जो आपके पहले कोड की दो दो पंक्तियों की तुलना में छोटा है (एक साथ घोषणा और लूप हेड)। इसलिए मैं फिर से कहता हूं - पुनरावृत्तियों का उपयोग करने और उनका उपयोग नहीं करने के बीच संक्षिप्तता में बहुत मौलिक अंतर नहीं है - आपने अभी भी एक forबयान के तीन हिस्सों को संतुष्ट किया है , भले ही आप उपयोग करें while: घोषित, पुनरावृत्ति, चेक समाप्ति।
इंजीनियर

16

चिंताओ का विभाजन

लूप के 'कोर' चिंता से पुनरावृति कोड को अलग करना बहुत अच्छा है। यह लगभग एक डिजाइन निर्णय है।

वास्तव में, सूचकांक द्वारा पुनरावृति आपको कंटेनर के कार्यान्वयन के लिए बाध्य करता है। कंटेनर को एक शुरुआत और अंत चलने वाले के लिए पूछना, अन्य कंटेनर प्रकारों के साथ उपयोग के लिए लूप कोड को सक्षम करता है।

इसके अलावा, std::for_eachजिस तरह से, आप संग्रह को क्या करना चाहते हैं, इसके बजाय इसके आंतरिक के बारे में कुछ पूछें

0x मानक क्लोजर पेश करने जा रहा है, जो इस दृष्टिकोण का उपयोग करने के लिए और अधिक आसान बना देगा - उदाहरण के लिए रूबी की अभिव्यंजक शक्ति पर एक नजर [1..6].each { |i| print i; }...

प्रदर्शन

लेकिन शायद एक बहुत बड़ा मुद्दा यह है कि, for_eachएप्रोच का उपयोग करने से पुनरावृत्ति को समानांतर रखने का अवसर मिलता है - इंटेल थ्रेडिंग ब्लॉक सिस्टम में प्रोसेसर की संख्या पर कोड ब्लॉक को वितरित कर सकता है!

नोट: algorithmsलाइब्रेरी की खोज के बाद , और विशेष रूप से foreach, मैं हास्यास्पद रूप से छोटे 'हेल्पर' ऑपरेटर स्ट्रक्च लिखने के दो या तीन महीनों से गुजरा, जो आपके साथी डेवलपर्स को पागल कर देगा। इस समय के बाद, मैं एक व्यावहारिक दृष्टिकोण पर लौट आया - छोटे लूप निकायों के लायक कोई और foreachनहीं :)

पुनरावृत्तियों पर एक संदर्भ पढ़ा जाना चाहिए "विस्तारित एसटीएल" पुस्तक है ।

GoF में Iterator पैटर्न के अंत में एक छोटा सा पैराग्राफ है, जो इस ब्रांड के पुनरावृत्ति के बारे में बात करता है; इसे 'आंतरिक पुनरावृत्ति' कहा जाता है। यहाँ एक नज़र है , भी।


15

क्योंकि यह अधिक वस्तु-उन्मुख है। यदि आप एक सूचकांक के साथ पुनरावृत्ति कर रहे हैं, जिसे आप मान रहे हैं:

क) उन वस्तुओं को आदेश दिया जाता है
बी) कि उन वस्तुओं को एक सूचकांक
सी द्वारा प्राप्त किया जा सकता है ) कि सूचकांक वृद्धि हर आइटम को मारा जाएगा
) कि सूचकांक शून्य पर शुरू होता है

एक पुनरावृत्त के साथ, आप कह रहे हैं "मुझे सब कुछ दे दो ताकि मैं इसके साथ काम कर सकूं" बिना यह जाने कि अंतर्निहित कार्यान्वयन क्या है। (जावा में, ऐसे संग्रह हैं जिन्हें एक सूचकांक के माध्यम से एक्सेस नहीं किया जा सकता है)

इसके अलावा, एक पुनरावृत्ति के साथ, सरणी के सीमा से बाहर जाने के बारे में चिंता करने की कोई आवश्यकता नहीं है।


2
मुझे नहीं लगता कि "ऑब्जेक्ट ओरिएंटेड" सही शब्द है। Iterators डिजाइन में "ऑब्जेक्ट ओरिएंटेड" नहीं हैं। वे ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग से अधिक कार्यात्मक प्रोग्रामिंग को बढ़ावा देते हैं, क्योंकि वे कक्षाओं से एल्गोरिदम को अलग करने के लिए प्रोत्साहित करते हैं।
विल्हेमटेल

इसके अलावा, पुनरावृत्तियों को सीमा से बाहर निकलने से बचने में मदद नहीं करता है। मानक एल्गोरिदम करते हैं, लेकिन पुनरावृत्तियों अकेले नहीं है।
विल्हेमटेल

काफी @wilhelmtell, मैं स्पष्ट रूप से जावा-केंद्रित दृष्टिकोण से इस बारे में सोच रहा हूं।
निंदक

1
और मुझे लगता है कि यह ओओ को बढ़ावा देता है, क्योंकि यह उस संग्रह के कार्यान्वयन से संग्रह पर परिचालन को अलग कर रहा है। वस्तुओं का एक संग्रह जरूरी नहीं पता होना चाहिए कि उनके साथ काम करने के लिए क्या एल्गोरिदम का उपयोग किया जाना चाहिए।
निंदक

वास्तव में वहाँ एसटीएल के संस्करण हैं जिन्होंने पुनरावृत्तियों की जाँच की है, जिसका अर्थ है कि जब आप उस पुनरावृत्त के साथ कुछ करने की कोशिश करते हैं, तो यह किसी प्रकार के अपवाद को छोड़ देगा।
डेमिन

15

पुनरावृत्तियों के बारे में एक और अच्छी बात यह है कि वे बेहतर ढंग से आपको अपनी कांस्ट-वरीयता को व्यक्त करने (और लागू करने) की अनुमति देते हैं। यह उदाहरण सुनिश्चित करता है कि आप अपने पाश के बीच में वेक्टर को बदल नहीं रहे हैं:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

यह उचित लगता है, लेकिन मुझे अभी भी संदेह है कि यदि ऐसा होने का कारण है const_iterator। यदि मैं वेक्टर को लूप में बदल देता हूं, तो मैं इसे एक कारण के लिए करता हूं, और 99.9% समय के लिए कि फेरबदल एक दुर्घटना नहीं है, और बाकी के लिए, यह सिर्फ एक बग है जैसे कि कोड में किसी भी प्रकार के कीड़े लेखक ठीक करने की जरूरत है। क्योंकि जावा, और कई अन्य भाषाओं में, कोई भी कॉन्स्टेंट ऑब्जेक्ट नहीं है, लेकिन उन भाषाओं के उपयोगकर्ताओं को उन भाषाओं में कोई कॉन्स्टिट सपोर्ट नहीं होने की समस्या है।
नीवेक 15

2
@neevek यदि ऐसा होने का कारण नहीं है const_iterator, तो पृथ्वी पर क्या कारण हो सकता है?
अंडरस्कोर_ड

@underscore_d, मैं भी हैरान हूं। मैं इस पर विशेषज्ञ नहीं हूं, यह सिर्फ इतना है कि इसका उत्तर मेरे लिए अजेय नहीं है।
neevek

15

सभी उत्कृष्ट उत्तर के अलावा ... intआपके वेक्टर के लिए पर्याप्त बड़ा नहीं हो सकता है। इसके बजाय, यदि आप अनुक्रमण का उपयोग करना चाहते हैं, तो size_typeअपने कंटेनर के लिए उपयोग करें :

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@Pat Notz, यह एक बहुत अच्छी बात है। एक एसटीएल-आधारित विंडोज एप्लिकेशन को x64 में पोर्ट करने के दौरान, मुझे संभवतः संभवतः छंटनी के कारण size_t को असाइन करने के बारे में सैकड़ों चेतावनियों से निपटना पड़ा है।
bk1e

1
इस तथ्य का उल्लेख नहीं करने के लिए कि आकार प्रकार अहस्ताक्षरित हैं और इंट पर हस्ताक्षर किए गए हैं, इसलिए आपके पास तुलना int iकरने के लिए गैर-सहज, बग-छुपा रूपांतरण है myvector.size()
एड्रियन मैक्कार्थी

12

मुझे शायद इशारा करना चाहिए कि आप फोन भी कर सकते हैं

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

एसटीएल पुनरावृत्तियां ज्यादातर वहां होती हैं ताकि एसटीएल एल्गोरिदम जैसे कंटेनर को स्वतंत्र किया जा सके।

यदि आप बस एक वेक्टर में सभी प्रविष्टियों पर लूप करना चाहते हैं तो बस इंडेक्स लूप स्टाइल का उपयोग करें।

अधिकांश मनुष्यों के लिए यह कम टाइपिंग और पार्स करना आसान है। अच्छा होगा यदि C ++ में टेम्प्लेट मैजिक के साथ ओवरबोर्ड जाने के बिना एक सरल फ़ॉरच लूप होगा।

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

मुझे नहीं लगता कि यह एक वेक्टर के लिए बहुत अंतर करता है। मैं स्वयं एक सूचकांक का उपयोग करना पसंद करता हूं क्योंकि मैं इसे अधिक पठनीय मानता हूं और आप जरूरत पड़ने पर 6 वस्तुओं को आगे बढ़ाने या पीछे की ओर कूदने जैसे यादृच्छिक उपयोग कर सकते हैं।

मैं लूप के अंदर की वस्तु का भी संदर्भ लेना पसंद करता हूं, ताकि जगह के आसपास बहुत सारे वर्ग ब्रैकेट न हों:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

एक इटरेटर का उपयोग करना अच्छा हो सकता है यदि आपको लगता है कि आपको भविष्य में किसी बिंदु पर वेक्टर को एक सूची के साथ बदलने की आवश्यकता हो सकती है और यह एसटीएल फ्रीक के लिए अधिक स्टाइलिश दिखता है, लेकिन मैं किसी अन्य कारण के बारे में नहीं सोच सकता।


अधिकांश एल्गोरिदम एक कंटेनर के प्रत्येक तत्व पर एक बार क्रमिक रूप से काम करते हैं। बेशक ऐसे अपवाद हैं जिनमें आप किसी विशिष्ट क्रम या तरीके से एक संग्रह को पार करना चाहते हैं, लेकिन इस मामले में मैं कड़ी मेहनत करूँगा और एक एल्गोरिथ्म लिखूंगा जो एसटीएल के साथ एकीकृत होता है और जो पुनरावृत्तियों के साथ काम करता है।
विल्हेमटेल

यह पुन: उपयोग को प्रोत्साहित करेगा और बाद में एक-एक त्रुटियों से बचना होगा। मैं उस एल्गोरिथ्म को पुनरावृत्तियों के साथ किसी भी अन्य मानक एल्गोरिथ्म की तरह कॉल करूंगा।
विल्हेमटेल

1
अग्रिम की भी जरूरत नहीं है ()। इटरेटर में एक ही + = और - = ऑपरेटरों को एक सूचकांक (वेक्टर और वेक्टर-जैसे कंटेनरों के लिए) के रूप में होता है।
एमएसलटर्स

I prefer to use an index myself as I consider it to be more readableकेवल कुछ स्थितियों में; दूसरों में, सूचकांक जल्दी से बहुत गड़बड़ हो जाता है। and you can do random accessजो सूचकांकों की एक अनूठी विशेषता नहीं है: en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d

3

दूसरा रूप यह दर्शाता है कि आप और अधिक सटीक क्या कर रहे हैं। अपने उदाहरण में, आप i के मूल्य के बारे में परवाह नहीं करते हैं, वास्तव में - आप जो चाहते हैं, वह इटरेटर में अगला तत्व है।


3

इस उत्तर के विषय पर थोड़ा और जानने के बाद, मुझे एहसास हुआ कि यह एक ओवरसिम्प्लीफिकेशन था। इस लूप के बीच का अंतर:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

और यह लूप:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

काफी न्यूनतम है। वास्तव में, इस तरह से लूप करने का सिंटैक्स मुझ पर बढ़ रहा है:

while (it != end){
    //do stuff
    ++it;
}

Iterators कुछ काफी शक्तिशाली घोषणात्मक विशेषताओं को अनलॉक करते हैं, और जब STL एल्गोरिदम लाइब्रेरी के साथ संयुक्त होते हैं, तो आप कुछ सुंदर शांत चीजें कर सकते हैं जो कि सरणी इंडेक्स व्यवस्थापक के दायरे से बाहर हैं।


सच्चाई यह है कि अगर सभी पुनरावृत्तियां आपके अंतिम उदाहरण के रूप में कॉम्पैक्ट थीं, तो बॉक्स के ठीक बाहर, मेरे पास उनके साथ कोई समस्या नहीं थी। बेशक, यह वास्तव में बराबर है for (Iter it = {0}; it != end; ++it) {...}- आपने घोषणा को छोड़ दिया है - इसलिए संक्षिप्तता आपके दूसरे उदाहरण से बहुत अलग नहीं है। फिर भी, +1।
इंजीनियर

3

अनुक्रमण को एक अतिरिक्त mulऑपरेशन की आवश्यकता होती है । उदाहरण के लिए, vector<int> vसंकलक में परिवर्तित हो v[i]जाता है &v + sizeof(int) * i


शायद ज्यादातर मामलों में पुनरावृत्तियों के सापेक्ष एक महत्वपूर्ण नुकसान नहीं है, लेकिन इसके बारे में पता होना अच्छी बात है।
नोबार

3
पृथक एकल तत्व अभिगम के लिए, शायद। लेकिन अगर हम लूप्स के बारे में बात कर रहे हैं - जैसे ओपी था - तो मुझे पूरा यकीन है कि यह उत्तर एक काल्पनिक गैर-अनुकूलन कंपाइलर पर आधारित है। किसी भी आधे-सभ्य व्यक्ति के पास पर्याप्त अवसर होगा और वह कैश करने की संभावना रखेगा sizeofऔर बस इसे हर बार एक बार जोड़ने के बजाय पुनरावृत्ति के अनुसार एक बार जोड़ देगा।
अंडरस्कोर_ड

2

पुनरावृत्ति के दौरान आपको संसाधित किए जाने वाले आइटम की संख्या जानने की आवश्यकता नहीं है। आपको बस आइटम की जरूरत है और पुनरावृत्तियां इस तरह की चीजों को बहुत अच्छा करती हैं।


2

किसी ने अभी तक यह उल्लेख नहीं किया है कि सूचकांकों का एक फायदा यह है कि जब आप किसी सन्निहित कंटेनर std::vectorको जोड़ते हैं तो वे अमान्य नहीं होते हैं , इसलिए आप पुनरावृत्ति के दौरान कंटेनर में आइटम जोड़ सकते हैं।

पुनरावृत्तियों के साथ भी यह संभव है, लेकिन आपको कॉल करना होगा reserve(), और इसलिए यह जानना होगा कि आप कितने आइटम जोड़ेंगे।


1

पहले से ही कई अच्छे अंक। मेरी कुछ अतिरिक्त टिप्पणियां हैं:

  1. मान लें कि हम C ++ मानक पुस्तकालय के बारे में बात कर रहे हैं, "वेक्टर" एक यादृच्छिक एक्सेस कंटेनर का अर्थ है जो कि सी-सरणी (यादृच्छिक अभिगम, कॉन्टिग्यूस मेमोरी लेआउट आदि) की गारंटी देता है। यदि आपने 'some_container' कहा होता, तो उपरोक्त कई उत्तर अधिक सटीक (कंटेनर इंडिपेंडेंस आदि) होते।

  2. संकलक अनुकूलन पर किसी भी निर्भरता को समाप्त करने के लिए, आप अनुक्रमित कोड में कुछ_vector.size () को लूप से बाहर स्थानांतरित कर सकते हैं, जैसे:

    const size_t numElems = some_vector.size ();
    for (size_t i = 0; i; 
  3. हमेशा पूर्व-वृद्ध पुनरावृत्तियों और असाधारण मामलों के रूप में बाद के वेतन वृद्धि का इलाज करें।

for (some_iterator = some_vector.begin (); some_iterator! = some_vectorend। (); ++ some_iterator) {// do stuff}

इसलिए std::vector<>कंटेनर की तरह ग्रहण करना और अनुक्रमित करना, एक से दूसरे को पसंद करने का कोई अच्छा कारण नहीं है, क्रमिक रूप से कंटेनर से गुजर रहा है। यदि आपको अक्सर पुराने या नए एलिमेंट इंडेक्स का उल्लेख करना है, तो अनुक्रमित संस्करण अधिक उपयुक्त है।

सामान्य तौर पर, पुनरावृत्तियों का उपयोग करना पसंद किया जाता है क्योंकि एल्गोरिदम उनका उपयोग करते हैं और व्यवहार को टाइप करने वाले को बदलकर (और कथित रूप से प्रलेखित) नियंत्रित किया जा सकता है। ऐरे स्थानों का उपयोग पुनरावृत्तियों के स्थान पर किया जा सकता है, लेकिन वाक्य-रचना अंतर बाहर होगा।


1

मैं पुनरावृत्तियों का उपयोग उसी कारण से नहीं करता, जिसके लिए मैं foreach-statement को नापसंद करता हूं। जब कई आंतरिक-छोर होते हैं, तो यह सभी स्थानीय मूल्यों और पुनरावृत्ति-नामों को याद रखने के बिना वैश्विक / सदस्य चर का ट्रैक रखने के लिए पर्याप्त कठिन होता है। मुझे जो उपयोगी लगता है वह है विभिन्न अवसरों के लिए सूचकांकों के दो सेटों का उपयोग करना:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

मैं उदाहरण के लिए कुछ रैंडम "anim_matrix" -ame-iterator "एनीमेशन_matrices [i]" जैसी चीजों को संक्षिप्त नहीं करना चाहता, क्योंकि तब आप यह स्पष्ट रूप से नहीं देख सकते कि यह मान किस सरणी से उत्पन्न हुआ है।


मैं यह नहीं देखता कि इस दृष्टि से सूचकांक कैसे बेहतर हैं। : आप आसानी से iterators इस्तेमाल कर सकते हैं और सिर्फ उनके नाम के लिए एक सम्मेलन लेने it, jt, ktयहां तक कि बस आदि, या का उपयोग जारी रखने i, j, k, आदि और अगर आप को पता है कि वास्तव में क्या पुनरावर्तक का प्रतिनिधित्व करता है, तो मेरे पास कुछ तरह की जरूरत है for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()और वर्णनात्मक होगा की तुलना में लगातार सूचकांक की तरह animation_matrices[animIndex][boneIndex]
अंडरस्कोर_ड

वाह, यह सदियों पहले की तरह लगता है जब मैंने वह राय लिखी थी। आजकल बहुत ज्यादा cringing के बिना foreach और c ++ iterators दोनों का उपयोग कर रहे हैं। मुझे लगता है कि वर्षों से बग्गी कोड के साथ काम करना किसी की सहिष्णुता को बढ़ाता है, इसलिए जब तक यह काम करता है, तब तक सभी वाक्यविन्यासों और सम्मेलनों को स्वीकार करना आसान होता है और जब तक आप घर जा सकते हैं;)
AareP

हाहा, वास्तव में, मैंने वास्तव में यह नहीं देखा कि यह पहले कितना पुराना था! कुछ और जो मैंने किसी तरह पिछली बार के बारे में नहीं सोचा था कि आजकल हमारे पास रेंज-आधारित forलूप भी है, जो इसे और भी संक्षिप्त करने के लिए इट्रेटर-आधारित तरीका बनाता है।
अंडरस्कोर_ड

1
  • यदि आप धातु के करीब रहना पसंद करते हैं / उनके कार्यान्वयन विवरण पर भरोसा नहीं करते हैं, तो पुनरावृत्तियों का उपयोग न करें
  • यदि आप नियमित रूप से विकास के दौरान दूसरे के लिए एक संग्रह प्रकार स्विच करते हैं, तो पुनरावृत्तियों का उपयोग करें
  • यदि आपको यह याद रखना मुश्किल है कि विभिन्न प्रकार के संग्रहों को कैसे पुनरावृत्त किया जाए (हो सकता है कि आपके पास उपयोग के कई अलग-अलग बाहरी स्रोतों से कई प्रकार हों), उन साधनों को एकजुट करने के लिए पुनरावृत्तियों का उपयोग करें जिनके द्वारा आप तत्वों पर चलते हैं। यह एक लिंक सूची को एक सरणी सूची के साथ स्विच करने के लिए लागू होता है।

वास्तव में, यही सब कुछ है। ऐसा नहीं है कि आप औसत रूप से अधिक संक्षिप्तता हासिल करने जा रहे हैं, और यदि संक्षिप्तता वास्तव में आपका लक्ष्य है, तो आप हमेशा मैक्रोज़ पर वापस गिर सकते हैं।


1

यदि आपके पास C ++ 11 सुविधाओं तक पहुंच है, तो आप अपने वेक्टर (या किसी अन्य कंटेनर) पर पुनरावृति के लिए रेंज-आधारित forलूप का उपयोग भी कर सकते हैं :

for (auto &item : some_vector)
{
     //do stuff
}

इस लूप का लाभ यह है कि आप वेक्टर के तत्वों को सीधे itemवेरिएबल के माध्यम से एक्सेस कर सकते हैं , बिना किसी इंडेक्स को गड़बड़ाए या एक डीटरफर किए हुए गलती करते समय। इसके अलावा, प्लेसहोल्डर autoआपको कंटेनर तत्वों के प्रकार को दोहराने से रोकता है, जो आपको कंटेनर-स्वतंत्र समाधान के करीब भी लाता है।

टिप्पणियाँ:

  • यदि आपको अपने लूप में तत्व इंडेक्स की आवश्यकता है और operator[]आपके कंटेनर के लिए मौजूद है (और आपके लिए काफी तेज है), तो बेहतर है कि आप अपने पहले रास्ते पर जाएं।
  • एक श्रेणी-आधारित forलूप का उपयोग कंटेनर में / से तत्वों को जोड़ने / हटाने के लिए नहीं किया जा सकता है। यदि आप ऐसा करना चाहते हैं, तो ब्रायन मैथ्यूज द्वारा दिए गए समाधान से बेहतर रहें ।
  • आप अपने कंटेनर में तत्वों को बदलने के लिए नहीं करना चाहते हैं, तो आप कीवर्ड का उपयोग करना चाहिए constइस प्रकार है: for (auto const &item : some_vector) { ... }

0

"सीपीयू को क्या करना है" बताने से भी बेहतर (अनिवार्यता) "पुस्तकालयों को जो आप चाहते हैं वह बता रहे हैं" (कार्यात्मक)।

इसलिए लूप का उपयोग करने के बजाय आपको स्टाल में मौजूद एल्गोरिदम को सीखना चाहिए।



0

मैं हमेशा ऐरे इंडेक्स का उपयोग करता हूं क्योंकि मेरे कई एप्लिकेशन को "डिस्प्ले थंबनेल इमेज" की तरह कुछ की आवश्यकता होती है। तो मैंने कुछ इस तरह लिखा:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

दोनों कार्यान्वयन सही हैं, लेकिन मैं 'लूप' के लिए पसंद करूंगा। जैसा कि हमने एक वेक्टर और किसी अन्य कंटेनर का उपयोग करने का फैसला किया है, इंडेक्स का उपयोग करना सबसे अच्छा विकल्प होगा। वैक्टर के साथ पुनरावृत्तियों का उपयोग करने से वस्तुओं को निरंतर मेमोरी ब्लॉकों में खोने का बहुत लाभ होगा जो उनकी पहुंच में आसानी में मदद करते हैं।


2
"वेक्टर्स के साथ पुनरावृत्तियों का उपयोग करने से वस्तुओं को निरंतर मेमोरी ब्लॉकों में रखने का बहुत लाभ होगा जो उनकी पहुंच में आसानी में मदद करते हैं।" [प्रशस्ति पत्र की जरूरत]। क्यों? क्या आपको लगता है कि एक सन्निहित कंटेनर को एक पुनरावृत्ति का वेतन वृद्धि एक साधारण जोड़ के रूप में लागू नहीं किया जा सकता है?
अंडरस्कोर_ड

0

मैंने महसूस किया कि यहाँ कोई भी उत्तर यह नहीं समझाता है कि क्यों मैं कंटेनरों में अनुक्रमण के बारे में एक सामान्य अवधारणा के रूप में पुनरावृत्तियों को पसंद करता हूं। ध्यान दें कि पुनरावृत्तियों का उपयोग करने का मेरा अधिकांश अनुभव वास्तव में C ++ से नहीं, बल्कि पायथन जैसी उच्च-स्तरीय प्रोग्रामिंग भाषाओं से आता है।

इट्रेटर इंटरफ़ेस आपके फ़ंक्शन के उपभोक्ताओं पर कम आवश्यकताओं को लागू करता है, जो उपभोक्ताओं को इसके साथ और अधिक करने की अनुमति देता है।

यदि आप सभी की जरूरत है कि आगे-आगे चलने में सक्षम हो, तो डेवलपर अनुक्रमित कंटेनरों का उपयोग करने के लिए सीमित नहीं है - वे किसी भी वर्ग के कार्यान्वयन का उपयोग कर सकते हैं operator++(T&), operator*(T)और operator!=(const &T, const &T)

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

आपका एल्गोरिथ्म उस मामले के लिए काम करता है जिसकी आपको आवश्यकता है - एक वेक्टर पर पुनरावृत्ति - लेकिन यह उन अनुप्रयोगों के लिए भी उपयोगी हो सकता है जिन्हें आप जरूरी नहीं समझते हैं:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

एक वर्ग-ब्रैकेट ऑपरेटर को लागू करने का प्रयास करना जो इस पुनरावृत्ति के समान कुछ करता है, को वंचित किया जाएगा, जबकि पुनरावृत्त कार्यान्वयन अपेक्षाकृत सरल है। स्क्वायर-ब्रैकेट्स ऑपरेटर आपकी कक्षा की क्षमताओं के बारे में भी निहितार्थ बनाता है - जिसे आप किसी भी मनमाने बिंदु पर अनुक्रमित कर सकते हैं - जिसे लागू करना मुश्किल या अक्षम हो सकता है।

Iterators भी खुद को सजावट के लिए उधार देते हैं । लोग पुनरावृत्तियों को लिख सकते हैं जो अपने निर्माता में एक पुनरावृत्ति लेते हैं और इसकी कार्यक्षमता बढ़ाते हैं:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

हालांकि ये खिलौने सांसारिक लग सकते हैं, एक साधारण इंटरफ़ेस के साथ शक्तिशाली चीजें करने के लिए पुनरावृत्तियों और पुनरावृत्त डेकोरेटर का उपयोग करने की कल्पना करना मुश्किल नहीं है - डेटाबेस के फॉरवर्ड-इट्रेटर के इट्रेटर के साथ केवल एक परिणाम से एक मॉडल ऑब्जेक्ट का निर्माण करना । ये पैटर्न अनंत सेट की स्मृति-कुशल पुनरावृत्ति को सक्षम करते हैं और, जैसा कि मैंने ऊपर लिखा एक फिल्टर के साथ, परिणामों का संभावित आलसी मूल्यांकन।

C ++ टेम्प्लेट की शक्ति का एक हिस्सा आपका इटरेटर इंटरफ़ेस है, जब फिक्स्ड-लेंथ सी सरणियों की पसंद पर लागू किया जाता है, सरल और कुशल पॉइंटर अंकगणित के लिए लागू होता है , जिससे यह वास्तव में शून्य-लागत अमूर्त होता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.