स्टैंडर्ड इटरेटर रेंज [आरंभ, अंत] के बजाय [आरंभ, अंत) क्यों हैं?


204

end()वास्तविक अंत के बजाय मानक अंतिम छोर के रूप में क्यों परिभाषित करता है ?


19
मैं अनुमान लगा रहा हूं "क्योंकि यही मानक कहता है" इसे नहीं काटेंगे, है ना? :)
लुचियन ग्रिगोर

39
@ लुचियानग्रिगर: बिल्कुल नहीं। यह मानक के पीछे (लोगों के लिए) हमारे सम्मान को नष्ट कर देगा। हमें उम्मीद करनी चाहिए कि मानक द्वारा किए गए विकल्पों का एक कारण है
केरेक एसबी

4
संक्षेप में, कंप्यूटर लोगों की तरह नहीं गिना जाता है। लेकिन अगर आप इस बात से उत्सुक हैं कि लोग कंप्यूटर की तरह क्यों नहीं गिनते हैं, तो मैं कुछ भी नहीं सुझाता हूं : एक प्राकृतिक इतिहास जीरो इन- -इन-थ्रू लुक के लिए मनुष्यों को पता था कि एक संख्या है जो एक कम है एक की अपेक्षा।
जॉन मैक्फर्लेन

8
क्योंकि "पिछले एक" को उत्पन्न करने का केवल एक ही तरीका है, यह अक्सर सस्ता नहीं होता है क्योंकि इसे वास्तविक होना होता है। "आप चट्टान के अंत में गिर गए" उत्पन्न करना हमेशा सस्ता होता है, कई संभव प्रतिनिधित्व करेंगे। (शून्य) * "आह्ह्ह्ह्ह्ह्ह्ह" ठीक रहेगा।
हंस पैसेंट

6
मैंने प्रश्न की तारीख को देखा और एक सेकंड के लिए मुझे लगा कि आप मजाक कर रहे हैं।
आसफ़

जवाबों:


286

आसानी से सबसे अच्छा तर्क है, जो खुद दिज्कस्ट्रा ने बनाया है :

  • आप चाहते हैं कि रेंज का आकार एक साधारण अंतर अंत हो  -  शुरू हो ;

  • निचली सीमा को शामिल करना अधिक "प्राकृतिक" है जब अनुक्रम खाली वाले को पतित करते हैं, और इसलिए भी कि विकल्प ( निचली सीमा को छोड़कर ) को "एक-पहले-शुरुआत" प्रहरी मूल्य के अस्तित्व की आवश्यकता होगी।

आपको अभी भी यह बताने की आवश्यकता है कि आप एक के बजाय शून्य पर क्यों गिनना शुरू करते हैं, लेकिन यह आपके प्रश्न का हिस्सा नहीं था।

[आरंभ, अंत) सम्मेलन के पीछे का ज्ञान समय और फिर से भुगतान करता है जब आपके पास किसी भी प्रकार का एल्गोरिदम होता है जो रेंज-आधारित निर्माणों के लिए कई नेस्टेड या पुनरावृत्त कॉलों से संबंधित होता है, जो स्वाभाविक रूप से चेन करते हैं। इसके विपरीत, एक डबल-क्लोज्ड रेंज का उपयोग करने से ऑफ-बायर्स और बेहद अप्रिय और शोर कोड होगा। उदाहरण के लिए, एक विभाजन पर विचार करें [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 )। एक अन्य उदाहरण मानक चलना लूप है for (it = begin; it != end; ++it), जो end - beginकई बार चलता है। यदि दोनों छोर समावेशी थे, तो संबंधित कोड बहुत कम पठनीय होगा - और कल्पना करें कि आप खाली श्रेणियों को कैसे संभालेंगे।

अंत में, हम यह भी एक अच्छा तर्क दे सकते हैं कि गिनती शून्य पर क्यों शुरू होनी चाहिए: हम जो बस स्थापित करते हैं, उसके लिए आधे खुले सम्मेलन के साथ, यदि आपको एन तत्वों की श्रेणी दी जाती है (किसी सरणी के सदस्यों को फिर से शुरू करने के लिए कहें), तो 0 प्राकृतिक "शुरुआत" है ताकि आप बिना किसी अजीब या सुधार के सीमा को [0, N ) के रूप में लिख सकें ।

संक्षेप में: तथ्य यह है कि हम 1श्रेणी-आधारित एल्गोरिदम में हर जगह संख्या नहीं देखते हैं और [प्रारंभ, अंत) सम्मेलन के लिए प्रेरणा का प्रत्यक्ष परिणाम है।


2
लूप के लिए विशिष्ट C आकार N की एक सरणी पर पुनरावृत्ति करता है "के लिए है (i = 0; मैं <N; i ++) [i] = 0;"। अब, आप इसे सीधे पुनरावृत्तियों के साथ व्यक्त नहीं कर सकते हैं - कई लोग व्यर्थ समय व्यर्थ करने की कोशिश कर रहे हैं। लेकिन यह कहना लगभग उतना ही स्पष्ट है "के लिए" (i = 0; i! = N; i ++) ... "0 मैपिंग शुरू करने के लिए और एन टू एंड इसलिए सुविधाजनक है।
क्रेजी गेलव

3
@KrazyGlew: मैंने जानबूझकर अपने पाश उदाहरण में प्रकार नहीं लगाए। आप के बारे में सोच तो beginऔर endके रूप में intमूल्यों के साथ रों 0और N, क्रमशः, यह बिल्कुल फिट बैठता है। यकीनन, यह ऐसी !=स्थिति है जो पारंपरिक की तुलना में अधिक स्वाभाविक है <, लेकिन हमने कभी यह नहीं पाया कि जब तक हम अधिक सामान्य संग्रह के बारे में सोचना शुरू नहीं करते।
केरेक एसबी

4
@KerrekSB: मैं इस बात से सहमत हूं कि "जब तक हमने और अधिक संग्रह के बारे में सोचना शुरू नहीं किया है, तब तक हमने कभी भी [[! बेहतर]] का दावा नहीं किया है।" IMHO कि चीजों में से एक है Stepanov के लिए श्रेय के हकदार हैं - कोई है जो एसटीएल से पहले इस तरह के टेम्पलेट पुस्तकालयों लिखने की कोशिश की। हालाँकि, मैं "=!" अधिक स्वाभाविक होने के बारे में बहस करूँगा - या, बल्कि, मैं तर्क करूँगा कि! = ने शायद बगों को पेश किया है, जो कि <b> होगा। के लिए सोचें (i = 0; मैं! = 100; मैं + = 3) ...
क्रेजी गेलव

@KrazyGlew: आपका अंतिम बिंदु कुछ ऑफ-टॉपिक है, क्योंकि अनुक्रम {0, 3, 6, ..., 99} उस फॉर्म का नहीं है जिसके बारे में ओपी ने पूछा था। यदि आप चाहते हैं कि यह इस प्रकार हो, तो आपको एक- ++पुनरावृत्त पुनरावृत्ति टेम्पलेट लिखना चाहिए step_by<3>, जो तब मूल रूप से विज्ञापित शब्दार्थ होगा।
केरेक एसबी

@KrazyGlew यहां तक ​​कि अगर <एक बग छिप जाएगा, वैसे भी यह एक बग है । यदि कोई उपयोग !=करना चाहिए जब वह उपयोग करना चाहिए <, तो यह एक बग है। वैसे, त्रुटि के राजा को यूनिट परीक्षण या दावे के साथ खोजना आसान है।
फिल 1970

80

वास्तव में, बहुत से पुनरावृत्त संबंधित सामान अचानक बहुत अधिक समझ में आता है यदि आप पुनरावृत्तियों को अनुक्रम के तत्वों पर इंगित नहीं करने पर विचार करते हैं , लेकिन बीच में , इसके साथ अगले तत्व को सही तरीके से एक्सेस करने के लिए डेरेफेरिंग करते हैं। फिर "वन पास्ट एंड" इट्रेटर अचानक समझ में आता है:

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^               ^
   |               |
 begin            end

स्पष्ट रूप beginसे अनुक्रम की शुरुआत की ओर इशारा करता है, और endउसी क्रम के अंत की ओर इशारा करता है। अपसंदर्भन beginतत्व तक पहुँचता है A, और dereferencing endहै क्योंकि यह करने के लिए कोई तत्व सही कोई मतलब नहीं है। इसके अलावा, iबीच में एक पुनरावृत्ति जोड़ना

   +---+---+---+---+
   | A | B | C | D |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
 begin     i      end

और आप तुरंत देखते हैं कि तत्वों की श्रेणी में तत्वों beginको iसमाहित किया गया है Aऔर Bजबकि तत्वों की श्रेणी में तत्वों iको endसमाहित किया गया है Cऔर D। Dereferencing iतत्व को इसका अधिकार देता है, दूसरे क्रम का पहला तत्व है।

यहां तक ​​कि रिवर्स iterators के लिए "ऑफ-बाय-वन" अचानक इस तरह से स्पष्ट हो जाता है: उस क्रम को उलट देना:

   +---+---+---+---+
   | D | C | B | A |
   +---+---+---+---+
   ^       ^       ^
   |       |       |
rbegin     ri     rend
 (end)    (i)   (begin)

मैंने नीचे दिए गए कोष्ठकों में संबंधित गैर-रिवर्स (आधार) पुनरावृत्तियों को लिखा है। आप देखें, रिवर्स इटरेटर से संबंधित i(जिसे मैंने नाम दिया है ri) अभी भी तत्वों Bऔर के बीच में इंगित करता है C। हालांकि अनुक्रम को उलटने के कारण, अब तत्व Bइसके दाईं ओर है।


2
यह IMHO सबसे अच्छा जवाब है, हालांकि मुझे लगता है कि यह बेहतर चित्रण हो सकता है यदि पुनरावृत्तियों ने संख्याओं पर ध्यान दिया, और तत्व संख्याओं के बीच थे (वाक्यविन्यास foo[i]) स्थिति के तुरंत बाद आइटम के लिए एक आशुलिपि है i)। इसके बारे में सोचते हुए, मुझे आश्चर्य होता है कि क्या भाषा के लिए "स्थिति के तुरंत बाद आइटम" और "स्थिति से पहले आइटम" के लिए अलग-अलग ऑपरेटर रखना उपयोगी हो सकता है, क्योंकि बहुत सारे एल्गोरिदम आसन्न वस्तुओं के जोड़े के साथ काम करते हैं, और कह रहे हैं " स्थिति i के दोनों ओर की वस्तुएं "I और i + 1 पर स्थित आइटम" की तुलना में क्लीनर हो सकती हैं।
सुपरकाट

@ सुपरकैट: संख्याओं को इटेटर पदों / सूचकांकों को इंगित नहीं करना था, बल्कि तत्वों को स्वयं इंगित करना था। मैं अक्षरों को बदलने के लिए अक्षरों को बदल दूंगा। वास्तव में, दिए गए अंकों के साथ, begin[0](एक यादृच्छिक अभिगमकर्ता मानकर) तत्व का उपयोग करेगा 1, क्योंकि 0मेरे उदाहरण अनुक्रम में कोई तत्व नहीं है ।
celtschk

"स्टार्ट" के बजाय "स्टार्ट" शब्द का इस्तेमाल क्यों किया जाता है? आखिरकार, "शुरू" एक क्रिया है।
user1741137

@ user1741137 मुझे लगता है कि "शुरुआत" का अर्थ "शुरुआत" (जो अब समझ में आता है) का संक्षिप्त नाम है। "शुरुआत" बहुत लंबा है, "शुरू" एक अच्छा फिट लगता है। "प्रारंभ" क्रिया "प्रारंभ" के साथ परस्पर विरोधी होगा (उदाहरण के लिए जब आपको start()किसी विशिष्ट प्रक्रिया को शुरू करने के लिए अपनी कक्षा में एक फ़ंक्शन को परिभाषित करना होगा या जो भी हो, अगर यह पहले से मौजूद एक के साथ संघर्ष करता है तो यह कष्टप्रद होगा)।
फेरेनोर

74

end()वास्तविक अंत के बजाय मानक अंतिम छोर के रूप में क्यों परिभाषित करता है ?

इसलिये:

  1. यह खाली श्रेणियों के लिए विशेष हैंडलिंग से बचा जाता है। खाली श्रेणियों के लिए, begin()के बराबर है end()&
  2. यह छोरों के लिए अंतिम मानदंड को सरल बनाता है जो तत्वों पर पुनरावृति करता है: छोरों को बस तब तक जारी रखा जाता है जब तक कि end()पहुंच न हो।

64

क्योंकि तब

size() == end() - begin()   // For iterators for whom subtraction is valid

और आपको अजीब चीजें नहीं करनी होंगी

// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }

और अगर आप गलती से नहीं लिखेगा गलत कोड की तरह

bool empty() { return begin() == end() - 1; }    // a typo from the first version
                                                 // of this post
                                                 // (see, it really is confusing)

bool empty() { return end() - begin() == -1; }   // Signed/unsigned mismatch
// Plus the fact that subtracting is also invalid for many iterators

इसके अलावा: यदि किसी वैध तत्व की ओर इशारा किया जाए तो क्या होगा ? find()end()
क्या आप वास्तव में चाहते हैं एक और सदस्य कहा जाता है invalid()जो गलत iterator देता है ?!
दो पुनरावृत्तियों पहले से ही काफी दर्दनाक है ...

ओह, और इस संबंधित पोस्ट को देखें


इसके अलावा:

यदि endअंतिम तत्व से पहले था, तो आप सच्चे अंत में कैसे होंगे insert()?!


2
यह एक बहुत ही कम जवाब है। उदाहरण संक्षिप्त और सीधे बिंदु पर हैं, और "इसके अलावा" किसी और के द्वारा नहीं कहा गया था और इस तरह की चीजें हैं जो पूर्वव्यापी में बहुत स्पष्ट लगती हैं लेकिन मुझे रहस्योद्घाटन की तरह मारा।
अंडरस्कोर_ड

@underscore_d: धन्यवाद !! :)
user541686

btw, अगर मैं उत्थान नहीं करने के लिए एक पाखंडी की तरह लग रहा हूँ, ऐसा इसलिए है क्योंकि मैं पहले ही जुलाई 2016 में वापस आ गया था!
अंडरस्कोर_ड

@underscore_d: हाहा मैंने नोटिस भी नहीं किया, लेकिन धन्यवाद! :)
user541686

22

आधा बंद पर्वतमाला [begin(), end())का इटेरियम मुहावरा मूल रूप से सादे सरणियों के लिए सूचक अंकगणितीय पर आधारित है। ऑपरेशन के उस मोड में, आपके पास फ़ंक्शंस होंगे जो एक सरणी और एक आकार पारित किए गए थे।

void func(int* array, size_t size)

[begin, end)जब आपके पास यह जानकारी हो तो आधी-बंद श्रेणियों में परिवर्तित करना बहुत सरल है:

int* begin;
int* end = array + size;

for (int* it = begin; it < end; ++it) { ... }

पूरी तरह से बंद श्रेणियों के साथ काम करने के लिए, यह कठिन है:

int* begin;
int* end = array + size - 1;

for (int* it = begin; it <= end; ++it) { ... }

चूँकि सरणियों में सरणियाँ C ++ में पुनरावृत्तियाँ होती हैं (और सिंटैक्स को इसे अनुमति देने के लिए डिज़ाइन किया गया था), इसे कॉल करने की std::find(array, array + size, some_value)तुलना में कॉल करना बहुत आसान है std::find(array, array + size - 1, some_value)


इसके अलावा, यदि आप आधी-बंद श्रेणियों के साथ काम करते हैं, तो आप !=अंतिम स्थिति की जांच के लिए ऑपरेटर का उपयोग कर सकते हैं, क्योंकि (यदि आपके ऑपरेटरों को सही तरीके से परिभाषित किया गया है) <का तात्पर्य है !=

for (int* it = begin; it != end; ++ it) { ... }

हालाँकि पूरी तरह से बंद श्रेणियों के साथ ऐसा करने का कोई आसान तरीका नहीं है। आप के साथ अटक रहे हैं <=

C ++ में समर्थन <और >संचालन करने वाला एकमात्र प्रकार का पुनरावृत्त रैंडम-एक्सेस पुनरावृत्त हैं। यदि आपको <=C ++ में प्रत्येक इट्रेटर क्लास के लिए एक ऑपरेटर लिखना था , तो आपको अपने सभी पुनरावृत्तियों को पूरी तरह से तुलनीय बनाना होगा, और आप कम सक्षम पुनरावृत्तियाँ बनाने के लिए कम विकल्प चुनेंगे (जैसे कि द्विदिश पुनरावृत्तियों पर std::list, या इनपुट सर्वर) iostreamsयदि C ++ पूरी तरह से बंद श्रेणियों का उपयोग करता है तो) पर काम करता है।


8

end()एक छोर को इंगित करने के साथ, लूप के लिए एक संग्रह को पुनरावृत्त करना आसान है:

for (iterator it = collection.begin(); it != collection.end(); it++)
{
    DoStuff(*it);
}

साथ end()पिछले तत्व की ओर इशारा करते, एक पाश और अधिक जटिल होगा:

iterator it = collection.begin();
while (!collection.empty())
{
    DoStuff(*it);

    if (it == collection.end())
        break;

    it++;
}

0
  1. यदि एक कंटेनर खाली है, begin() == end()
  2. C ++ प्रोग्रामर लूप की स्थिति में !=इसके बजाय <(कम से कम) का उपयोग करते हैं, इसलिए end()एक स्थिति की ओर इशारा करना सुविधाजनक होता है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.