end()
वास्तविक अंत के बजाय मानक अंतिम छोर के रूप में क्यों परिभाषित करता है ?
end()
वास्तविक अंत के बजाय मानक अंतिम छोर के रूप में क्यों परिभाषित करता है ?
जवाबों:
आसानी से सबसे अच्छा तर्क है, जो खुद दिज्कस्ट्रा ने बनाया है :
आप चाहते हैं कि रेंज का आकार एक साधारण अंतर अंत हो - शुरू हो ;
निचली सीमा को शामिल करना अधिक "प्राकृतिक" है जब अनुक्रम खाली वाले को पतित करते हैं, और इसलिए भी कि विकल्प ( निचली सीमा को छोड़कर ) को "एक-पहले-शुरुआत" प्रहरी मूल्य के अस्तित्व की आवश्यकता होगी।
आपको अभी भी यह बताने की आवश्यकता है कि आप एक के बजाय शून्य पर क्यों गिनना शुरू करते हैं, लेकिन यह आपके प्रश्न का हिस्सा नहीं था।
[आरंभ, अंत) सम्मेलन के पीछे का ज्ञान समय और फिर से भुगतान करता है जब आपके पास किसी भी प्रकार का एल्गोरिदम होता है जो रेंज-आधारित निर्माणों के लिए कई नेस्टेड या पुनरावृत्त कॉलों से संबंधित होता है, जो स्वाभाविक रूप से चेन करते हैं। इसके विपरीत, एक डबल-क्लोज्ड रेंज का उपयोग करने से ऑफ-बायर्स और बेहद अप्रिय और शोर कोड होगा। उदाहरण के लिए, एक विभाजन पर विचार करें [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 )। एक अन्य उदाहरण मानक चलना लूप है for (it = begin; it != end; ++it)
, जो end - begin
कई बार चलता है। यदि दोनों छोर समावेशी थे, तो संबंधित कोड बहुत कम पठनीय होगा - और कल्पना करें कि आप खाली श्रेणियों को कैसे संभालेंगे।
अंत में, हम यह भी एक अच्छा तर्क दे सकते हैं कि गिनती शून्य पर क्यों शुरू होनी चाहिए: हम जो बस स्थापित करते हैं, उसके लिए आधे खुले सम्मेलन के साथ, यदि आपको एन तत्वों की श्रेणी दी जाती है (किसी सरणी के सदस्यों को फिर से शुरू करने के लिए कहें), तो 0 प्राकृतिक "शुरुआत" है ताकि आप बिना किसी अजीब या सुधार के सीमा को [0, N ) के रूप में लिख सकें ।
संक्षेप में: तथ्य यह है कि हम 1
श्रेणी-आधारित एल्गोरिदम में हर जगह संख्या नहीं देखते हैं और [प्रारंभ, अंत) सम्मेलन के लिए प्रेरणा का प्रत्यक्ष परिणाम है।
begin
और end
के रूप में int
मूल्यों के साथ रों 0
और N
, क्रमशः, यह बिल्कुल फिट बैठता है। यकीनन, यह ऐसी !=
स्थिति है जो पारंपरिक की तुलना में अधिक स्वाभाविक है <
, लेकिन हमने कभी यह नहीं पाया कि जब तक हम अधिक सामान्य संग्रह के बारे में सोचना शुरू नहीं करते।
++
पुनरावृत्त पुनरावृत्ति टेम्पलेट लिखना चाहिए step_by<3>
, जो तब मूल रूप से विज्ञापित शब्दार्थ होगा।
!=
करना चाहिए जब वह उपयोग करना चाहिए <
, तो यह एक बग है। वैसे, त्रुटि के राजा को यूनिट परीक्षण या दावे के साथ खोजना आसान है।
वास्तव में, बहुत से पुनरावृत्त संबंधित सामान अचानक बहुत अधिक समझ में आता है यदि आप पुनरावृत्तियों को अनुक्रम के तत्वों पर इंगित नहीं करने पर विचार करते हैं , लेकिन बीच में , इसके साथ अगले तत्व को सही तरीके से एक्सेस करने के लिए डेरेफेरिंग करते हैं। फिर "वन पास्ट एंड" इट्रेटर अचानक समझ में आता है:
+---+---+---+---+
| 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
इसके दाईं ओर है।
foo[i]
) स्थिति के तुरंत बाद आइटम के लिए एक आशुलिपि है i
)। इसके बारे में सोचते हुए, मुझे आश्चर्य होता है कि क्या भाषा के लिए "स्थिति के तुरंत बाद आइटम" और "स्थिति से पहले आइटम" के लिए अलग-अलग ऑपरेटर रखना उपयोगी हो सकता है, क्योंकि बहुत सारे एल्गोरिदम आसन्न वस्तुओं के जोड़े के साथ काम करते हैं, और कह रहे हैं " स्थिति i के दोनों ओर की वस्तुएं "I और i + 1 पर स्थित आइटम" की तुलना में क्लीनर हो सकती हैं।
begin[0]
(एक यादृच्छिक अभिगमकर्ता मानकर) तत्व का उपयोग करेगा 1
, क्योंकि 0
मेरे उदाहरण अनुक्रम में कोई तत्व नहीं है ।
start()
किसी विशिष्ट प्रक्रिया को शुरू करने के लिए अपनी कक्षा में एक फ़ंक्शन को परिभाषित करना होगा या जो भी हो, अगर यह पहले से मौजूद एक के साथ संघर्ष करता है तो यह कष्टप्रद होगा)।
end()
वास्तविक अंत के बजाय मानक अंतिम छोर के रूप में क्यों परिभाषित करता है ?
इसलिये:
begin()
के बराबर है
end()
& end()
पहुंच न हो।क्योंकि तब
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()
?!
आधा बंद पर्वतमाला [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 ++ पूरी तरह से बंद श्रेणियों का उपयोग करता है तो) पर काम करता है।
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++;
}
begin() == end()
।!=
इसके बजाय <
(कम से कम) का उपयोग करते हैं, इसलिए end()
एक स्थिति की ओर इशारा करना सुविधाजनक होता है।