सामग्री पर जाएँ
Topos Uranos

सारांश

std::array के लिए कंपाइल के समय यह जानना ज़रूरी है कि उसमें कितने अवयव होंगे; दूसरी ओर, अधिकांश समस्याएँ यह तब तक नहीं बतातीं जब तक डेटा आ न जाए। यह पाठ std::vector को प्रस्तुत करता है, वह अनुक्रम जो डेटा के अनुसार बढ़ता है: उसे कैसे बनाया जाता है, push_back से उसमें अवयव कैसे जोड़े जाते हैं, और उससे अज्ञात लंबाई की शृंखलाएँ कैसे पढ़ी जाती हैं। इसके बाद संग्रहों के साथ हर प्रोग्रामिंग के तीन मानक ट्रैवर्सल आते हैं (संचय, फ़िल्टर और रूपांतरण), लूप से भी और लाइब्रेरी के एल्गोरिदम से भी; फिर किसी फ़ंक्शन को वेक्टर पास करना, और यह कि नया वेक्टर लौटाना सस्ता क्यों है। पाठ वेक्टर के भीतरी ढाँचे के साथ समाप्त होता है: उसका आकार और उसकी क्षमता, वह ब्लॉक जो भर जाने पर दुगुने आकार के दूसरे ब्लॉक से बदल दिया जाता है, और किसी अवयव का वह रेफ़रेंस जो इसी कारण मुक्त की जा चुकी मेमोरी की ओर इशारा करता रह सकता है।

अधिगम उद्देश्य

  1. std::vector बनाना, push_back से उसमें अवयव जोड़ना और उसे ट्रैवर्स करना, तथा उसकी सहायता से अज्ञात लंबाई की डेटा-शृंखला पढ़ना।
  2. ऐसे फ़ंक्शन लिखना जो वेक्टर को पढ़ने के लिए कॉन्स्ट रेफ़रेंस द्वारा और बदलने के लिए रेफ़रेंस द्वारा लें, और परिणाम अनुक्रम होने पर नया वेक्टर लौटाएँ।
  3. संचय, फ़िल्टर और रूपांतरण के ट्रैवर्सल लागू करना, किसी समस्या में उसके अनुरूप पैटर्न पहचानना, और जब लाइब्रेरी अनुमति दे, तब उसे std::ranges::count, std::ranges::max या std::ranges::fold_left से संक्षिप्त करना।
  4. किसी उदाहरण से वेक्टर के आकार और क्षमता का अंतर समझाना, और यह कि कोई दूसरा अवयव जोड़ने पर किसी अवयव का रेफ़रेंस अमान्य क्यों हो सकता है।

बढ़ने वाला अनुक्रम

वेक्टर बनाना और भरना

पाठ ऐरे ने इंडेक्स वाले अनुक्रम को प्रस्तुत किया: एक ही प्रकार के अवयव, एक के बाद एक, शून्य से क्रमांकित। उसका नियत आकार वाला आधुनिक रूप, std::array<int, 7>, आकार को प्रकार में रखता है, और कंपाइलर को उसे जानना होता है। परंतु जो व्यक्ति किसी कक्षा के अंकों का औसत निकालने वाला प्रोग्राम लिखता है, वह नहीं जानता कि उसमें कितने विद्यार्थी होंगे। इससे ऐसे अनुक्रम की आवश्यकता निकलती है जिसका आकार एक और डेटा हो, जो निष्पादन के दौरान बदले: यही वेक्टर है, std::vector<T>, हेडर <vector> से, जहाँ T अवयवों का प्रकार है।

C++ प्रोग्राम
#include <print>#include <vector>​int main(){    std::vector<int> squares{};    std::println("शुरू में: आकार {}, खाली: {}", squares.size(), squares.empty());    for (int k{1}; k <= 5; ++k) {        squares.push_back(k * k);    }    std::println("{}", squares);    std::println("आकार {}, पहला {}, अंतिम {}", squares.size(), squares[0], squares.back());    squares[1] = 0;    squares.pop_back();    std::println("{}", squares);}
आउटपुट
शुरू में: आकार 0, खाली: true[1, 4, 9, 16, 25]आकार 5, पहला 1, अंतिम 25[1, 0, 9, 16]

पंक्ति 6 पूर्णांकों का एक खाली वेक्टर बनाती है: ऐरे के विपरीत, पहले से यह बताना ज़रूरी नहीं कि उसमें कितने अवयव होंगे। size() लौटाता है कि उसमें कितने अवयव हैं, और empty() बताता है कि उसमें कोई अवयव नहीं है या नहीं। पंक्तियों 8 से 10 का लूप push_back से पाँच वर्ग जोड़ता है, जो अपने आर्ग्युमेंट को अंत में रखता है और आकार को एक बढ़ा देता है। पंक्ति 11 पूरे वेक्टर को रेंज के उस फ़ॉर्मैट में लिखती है जिसे इससे पहले के पाठ ने ऐरे के साथ इस्तेमाल किया था। इंडेक्स द्वारा पहुँच वही पुरानी है, और squares.back(), squares[squares.size() - 1] का संक्षिप्त रूप है। पंक्ति 13 किसी मौजूदा अवयव को मान असाइन करती है, और पंक्ति 14 pop_back से अंतिम अवयव हटाती है। संक्षेप में, इस पाठ की संक्रियाएँ ये हैं।

  • v.push_back(x), x को अंत में जोड़ता है; v.pop_back() अंतिम अवयव हटाता है (खाली वेक्टर के साथ यह अपरिभाषित व्यवहार है)।
  • v.size() आकार है, std::size_t प्रकार का, आकारों का वह अचिह्नित पूर्णांक जिसे पाठ ऐरे ने प्रस्तुत किया था; v.empty(), v.size() == 0 के तुल्य है।
  • v[i] और v.at(i) इंडेक्स i वाला अवयव देते हैं, जिसे i < v.size() को संतुष्ट करना चाहिए; v.front() और v.back() पहला और अंतिम अवयव देते हैं, और माँगते हैं कि वेक्टर खाली न हो।
  • v.resize(n) वेक्टर में n अवयव छोड़ता है: अंत में बचे अतिरिक्त अवयव हटाता है या अंत में ऐसे अवयव जोड़ता है जो खाली कर्ली कोष्ठकों की तरह इनिशियलाइज़ किए गए हों (संख्याओं के लिए शून्य)।

ऐरे की तरह, v[i] किसी जाँच की गारंटी नहीं देता, और v.at(i) देता है: इंडेक्स 7 और पाँच अवयवों के साथ, वह प्रोग्राम को संदेश vector::_M_range_check: __n (which is 7) >= this->size() (which is 5) के साथ रोक देता है, जो इंडेक्स और आकार बताता है। इस रुकावट को उत्पन्न करने वाले तंत्र, एक्सेप्शन, का अध्ययन C++ में प्रोग्रामिंग II में होता है।

कर्ली कोष्ठक और गोल कोष्ठक

पाठ्यक्रम हर चीज़ को कर्ली कोष्ठकों से इनिशियलाइज़ करता है, और वेक्टर पाठ्यक्रम का पहला प्रकार है जिसमें कर्ली कोष्ठकों की जगह गोल कोष्ठक रखने से परिणाम बदल जाता है। कर्ली कोष्ठकों के साथ, लिखे गए मान ही अवयव हैं; गोल कोष्ठकों के साथ, पहली संख्या अवयवों की संख्या है और दूसरी, यदि हो, तो हर अवयव का मान।

C++ प्रोग्राम
#include <print>#include <vector>​int main(){    const std::vector<int> a{5};    const std::vector<int> b(5);    const std::vector<int> c{3, 7};    const std::vector<int> d(3, 7);    std::println("{} {} {} {}", a, b, c, d);}
आउटपुट
[5] [0, 0, 0, 0, 0] [3, 7] [7, 7, 7]

a में एक अवयव है, 5; b में पाँच शून्य; c में 3 और 7; d में तीन बार 7। अतः कर्ली कोष्ठकों के भीतर की सूची कहती है “ये अवयव हैं”, और गोल कोष्ठक एक ही मान की n प्रतियाँ माँगने के लिए रखे जाते हैं, जैसे शून्य पर रखे काउंटरों का वेक्टर।

खाली वेक्टर का अंतिम अवयव

खाली वेक्टर एक वैध स्थिति है (बिना डेटा का इनपुट, वह फ़िल्टर जिससे कोई नहीं गुज़रता), और उसके आकार वाले हर सूत्र को इस स्थिति में जाँचना चाहिए; सबसे धोखेबाज़ v.size() - 1 है। चूँकि v.size() एक अचिह्नित पूर्णांक है, उसके 0 होने पर उसमें से 1 घटाने से −1-1 नहीं मिलता, बल्कि वह घूमकर, जैसा कि पाठ पूर्णांक और उनका निरूपण में देखा गया, std::size_t के सबसे बड़े मान पर पहुँच जाता है।

C++ प्रोग्राम
#include <cstddef>#include <print>#include <vector>​int main(){    const std::vector<int> values{};    std::println("values.size() - 1 = {}", values.size() - 1);    // ग़लत सीमा: खाली वेक्टर के साथ शर्त सत्य है।    for (std::size_t i{0}; i <= values.size() - 1; ++i) {        std::println("{}", values[i]);    }}
आउटपुट
values.size() - 1 = 18446744073709551615/usr/include/c++/15/bits/stl_vector.h:1282: constexpr std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::const_reference std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::operator[](size_type) const [with _Tp = int; _Alloc = std::allocator<int>; const_reference = const int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed.

सिस्टम ने प्रोग्राम को सिग्नल 6 से रोक दिया।

आउटपुट की पहली पंक्ति 18 446 744 073 709 551 61518\,446\,744\,073\,709\,551\,615 है, अर्थात 264−12^{64} - 1, इस प्लेटफ़ॉर्म पर std::size_t का सबसे बड़ा मान। इससे यह निकलता है कि लूप की शर्त, 0 <= 18446744073709551615, सत्य है, और पहला चक्कर ऐसे वेक्टर का values[0] पढ़ता है जिसमें अवयव 0 है ही नहीं: अपरिभाषित व्यवहार। जो दिखता है वह विकल्पों पर निर्भर करता है, ऐरे की तरह: बिना ऑप्टिमाइज़ेशन के कंपाइल करने पर, GCC 15 की लाइब्रेरी operator[] का इंडेक्स जाँचती है और प्रोग्राम को एक अभिकथन से रोक देती है जो __n < this->size() कहता है; ऑप्टिमाइज़ेशन के साथ यह जाँच ग़ायब हो जाती है, और लूप, जो समय पर नहीं रुकता, वहाँ पढ़ने की कोशिश करता है जहाँ कोई अवयव नहीं है (Linux पर -O2 के साथ पुष्टि की गई: चूँकि खाली वेक्टर का कोई ब्लॉक नहीं होता, प्रोग्राम सेगमेंटेशन फ़ॉल्ट के साथ समाप्त होता है, कारण समझाने वाले किसी संदेश के बिना)। सुधार यह है कि सीमा को घटाव के बिना लिखा जाए, i < values.size(), जो खाली वेक्टर के साथ शुरू से ही असत्य है; और जहाँ घटाव ज़रूरी लगे, वहाँ पहले values.empty() पूछा जाए।