सादे सरणियों के लिए काम के लिए रेंज-आधारित कैसे होता है?


87

C ++ 11 में आप एक श्रेणी-आधारित का उपयोग कर सकते हैं for, जो foreachअन्य भाषाओं के रूप में कार्य करता है । यह सादे सी सरणियों के साथ भी काम करता है:

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

कैसे पता चलता है कि कब रुकना है? क्या यह केवल स्थिर सरणियों के साथ काम करता है जिसे उसी दायरे में घोषित किया गया forहै जिसका उपयोग किया जाता है? आप इसे forगतिशील सरणियों के साथ कैसे उपयोग करेंगे ?


10
C या C ++ प्रति se में कोई "डायनेमिक" सरणियाँ नहीं हैं - सरणी प्रकार हैं और फिर ऐसे पॉइंटर्स हैं जो किसी ऐरे की ओर इशारा नहीं कर सकते हैं या स्मृति के डायनेमिक रूप से आवंटित ब्लॉक हो सकते हैं जो अधिकतर एरे की तरह व्यवहार करते हैं। T [n] के किसी भी प्रकार के लिए, इसका आकार प्रकार में एन्कोड किया गया है और इसके द्वारा पहुँचा जा सकता है for। लेकिन जिस क्षण सरणी सूचक को इंगित करता है, आकार की जानकारी खो जाती है।
JohannesD

1
अपने उदाहरण में, तत्वों की संख्या में numbersहै sizeof(numbers)/sizeof(int)उदाहरण के लिए,।
JohannesD

जवाबों:


57

यह किसी भी अभिव्यक्ति के लिए काम करता है जिसका प्रकार एक सरणी है। उदाहरण के लिए:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

अधिक विस्तृत विवरण के लिए, यदि अभिव्यक्ति का प्रकार दाईं ओर दिया गया :है, तो यह सरणी प्रकार है, तो लूप इसमें से पुनरावृत्त होता ptrहै ptr + size( ptrसरणी के पहले तत्व को इंगित करते हुए, सरणी sizeका तत्व गणना)।

यह उपयोगकर्ता द्वारा परिभाषित प्रकारों के विपरीत है, जो किसी वर्ग वस्तु या (यदि उस तरह का कोई सदस्य नहीं है) गैर-सदस्य कार्यों से गुजरने पर सदस्य के रूप में देखते हैं beginऔर काम करते हैं end। वे फ़ंक्शंस आरंभ और समाप्ति पुनरावृत्तियों (क्रमशः अंतिम तत्व और क्रमशः अनुक्रम की शुरुआत के बाद इंगित करते हुए) प्राप्त करेंगे।

यह प्रश्न स्पष्ट करता है कि यह अंतर क्यों मौजूद है।


8
मुझे लगता है कि सवाल था कि कैसे यह काम करता है, नहीं जब यह काम करता है
sehe

1
@ इस प्रश्न में कई '?' es शामिल थे। एक था "क्या यह साथ काम करता है ...?"। मैंने दोनों को समझाया कि यह कैसे और कब काम करता है।
जोहान्स शाउब -

8
@JohannesSchaub: मुझे लगता है कि "कैसे" मुद्दा यहाँ है कि कैसे आप पहली बार में एक सरणी प्रकार के ऑब्जेक्ट का आकार प्राप्त करते हैं (क्योंकि संकेत बनाम सरणियों भ्रम के कारण, लगभग हर कोई नहीं जानता है कि एक सरणी का आकार प्रोग्रामर के लिए उपलब्ध)।
JohannesD

मेरा मानना है कि यह केवल गैर-सदस्य के लिए लग रहा है begin`अंत . It just happens that std :: शुरू `std::endसदस्य कार्यों का उपयोग, और अगर एक बेहतर मैच उपलब्ध नहीं है इस्तेमाल किया जाएगा।
डेनिस ज़िकफ़ोज़

3
@ मैड्रिड में मैडिसन नहीं, इसे बदलने और सदस्यों को शुरू करने और समाप्त करने का समर्थन करने का निर्णय लिया गया। शुरू करने और समाप्त करने के पक्ष में सदस्यों ने अस्पष्टताएं पैदा नहीं कीं, जिससे बचना मुश्किल है।
जोहान्स शाउब -

43

मुझे लगता है कि इस सवाल का सबसे महत्वपूर्ण हिस्सा है, कैसे सी ++ जानता है कि एक सरणी का आकार क्या है (कम से कम मैं यह जानना चाहता था कि मुझे यह सवाल कब मिला)।

C ++ एक सरणी का आकार जानता है, क्योंकि यह सरणी की परिभाषा का एक हिस्सा है - यह चर का प्रकार है। एक कंपाइलर को टाइप जानना होता है।

चूंकि C ++ 11 std::extentका उपयोग किसी सरणी के आकार को प्राप्त करने के लिए किया जा सकता है:

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

बेशक, इसका कोई मतलब नहीं है, क्योंकि आपको पहली पंक्ति में स्पष्ट रूप से आकार प्रदान करना होगा, जिसे आप दूसरी पंक्ति में प्राप्त करते हैं। लेकिन आप भी उपयोग कर सकते हैं decltypeऔर फिर यह अधिक दिलचस्प हो जाता है:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;

6
यह वास्तव में है जो मैं मूल रूप से पूछ रहा था। :)
पॉल मंटा

19

नवीनतम C ++ वर्किंग ड्राफ्ट (n3376) के अनुसार स्टेटमेंट के लिए राउंड निम्न के बराबर है:

{
    auto && __range = range-init;
    for (auto __begin = begin-expr,
              __end = end-expr;
            __begin != __end;
            ++__begin) {
        for-range-declaration = *__begin;
        statement
    }
}

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

मुझे लगता है कि आप उपरोक्त सिंटैक्स का उपयोग करने के लिए एरे के साथ एक तरीका प्रदान करने के लिए निम्नलिखित जैसी कोई चीज़ देख सकते हैं जिसमें केवल एक पॉइंटर और आकार (डायनेमिक एरेज़) शामिल हैं:

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

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

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

यह वाक्यविन्यास, मेरी राय में, आप का उपयोग std::for_eachया एक सादे forलूप की तुलना में बहुत स्पष्ट है ।


3

यह जानता है कि कब रुकना है क्योंकि यह स्थिर सरणियों की सीमा जानता है।

मुझे यकीन नहीं है कि "डायनेमिक सरणियों" से आपका क्या तात्पर्य है, यदि किसी मामले में, स्थिर सरणियों पर पुनरावृत्ति न हो, तो अनौपचारिक रूप से, कंपाइलर नामों को देखता है beginऔर endउस वस्तु के वर्ग के दायरे में जिसे आप ओवररेट करते हैं, या दिखता है तर्क-निर्भर लुकअप के लिए begin(range)और end(range)उपयोग करने के लिए और पुनरावृत्तियों के रूप में उनका उपयोग करता है।

अधिक जानकारी के लिए, C ++ 11 मानक (या उसके सार्वजनिक मसौदे) में, "6.5.4 रेंज-आधारित forकथन", pg.145


4
एक "डायनेमिक एरे" एक के साथ बनाया जाएगा new[]। उस स्थिति में, आपको केवल एक संकेतक मिला है जिसमें आकार का कोई संकेत नहीं है, इसलिए इसके forसाथ काम करने के लिए रेंज-आधारित के लिए कोई रास्ता नहीं है।
माइक सेमोर

मेरे जवाब में एक गतिशील सरणी शामिल है जिसका आकार (4) संकलन समय पर जाना जाता है, लेकिन मुझे नहीं पता कि "डायनेमिक सरणी" की वह व्याख्या क्या प्रश्नकर्ता का इरादा है।
जोहान्स स्काउब -

3

सादे सरणियों के लिए काम के लिए रेंज-आधारित कैसे होता है?

यह है कि के रूप में पढ़ने के लिए, " मुझे बताओ कि क्या एक के लिए (सरणियों के साथ) करता है? "

मैं यह मानकर उत्तर दूंगा कि - नेस्टेड सरणियों का उपयोग करके निम्नलिखित उदाहरण लें:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

पाठ संस्करण:

iaसरणियों की एक सरणी है ("नेस्टेड ऐरे"), जिसमें [3]एरे होते हैं, जिसमें प्रत्येक [4]मान होते हैं। उपरोक्त उदाहरण iaप्राथमिक 'रेंज' ( [3]) के माध्यम से लूप करता है , और इसलिए [3]कई बार लूप करता है। प्रत्येक पाश में से एक का उत्पादन iaकी [3]युक्त एक सरणी - प्राथमिक मूल्यों पहले से शुरू है और पिछले के साथ समाप्त [4]मान।

  • पहला लूप: plबराबर {1,2,3,4}- एक सरणी
  • दूसरा लूप: plबराबर {5,6,7,8}- एक सरणी
  • तीसरा लूप: plबराबर {9,10,11,12}- एक सरणी

इससे पहले कि हम इस प्रक्रिया की व्याख्या करें, यहाँ सरणियों के बारे में कुछ दोस्ताना अनुस्मारक दिए गए हैं:

  • Arrays को उनके पहले मूल्य के संकेत के रूप में व्याख्यायित किया जाता है - किसी भी पुनरावृत्ति के बिना किसी सरणी का उपयोग करके पहले मान का पता दिया जाता है
  • pl एक संदर्भ होना चाहिए क्योंकि हम सरणियों की प्रतिलिपि नहीं बना सकते हैं
  • सरणियों के साथ, जब आप सरणी ऑब्जेक्ट में खुद को एक संख्या जोड़ते हैं, तो यह आगे बढ़ता है कि कई बार और बराबर प्रविष्टि के लिए 'अंक' - यदि nप्रश्न में संख्या है, तो ia[n]उसी के रूप में है*(ia+n) (हम nप्रविष्टियों को दर्ज करने वाले पते को डीरफेर कर रहे हैं) आगे), और ia+nजैसा है &ia[n](हम सरणी में उस प्रविष्टि का पता प्राप्त कर रहे हैं) के समान है।

यहाँ क्या हो रहा है:

  • प्रत्येक लूप पर , के साथ plएक संदर्भ के रूप में सेट किया गया हैia[n]n वर्तमान लूप काउंट 0. तो से शुरू की बराबरी, plहै ia[0]दूसरा यह है पर, पहले दौर पर ia[1], और इतने पर। यह पुनरावृत्ति के माध्यम से मान निकालता है।
  • पाश इतने लंबे समय तक चला जाता है ia+n कम है end(ia)

... और वह इसके बारे में है।

यह वास्तव में यह लिखने का एक सरल तरीका है :

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int n = 0; n != 3; ++n)
  auto &pl = ia[n];

यदि आपकी सरणी नेस्टेड नहीं है, तो यह प्रक्रिया थोड़ी सरल हो जाती है कि संदर्भ की आवश्यकता नहीं है, क्योंकि पुनरावृत्त मूल्य एक सरणी नहीं है, बल्कि एक 'सामान्य' मान है:

 int ib[3] = {1,2,3};

 // short
 for (auto pl : ib)
   cout << pl;

 // long
 for (int n = 0; n != 3; ++n)
   cout << ib[n];

कुछ अतिरिक्त जानकारी

क्या होगा अगर हम autoबनाते समय कीवर्ड का उपयोग नहीं करना चाहते हैंpl ? वो कैसा लगता है?

निम्नलिखित उदाहरण में, plएक को संदर्भित करता है array of four integers। प्रत्येक लूप पर plमूल्य दिया जाता है ia[n]:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int (&pl)[4] : ia)

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


@ और 10 में से 9 बार खिताब है जो Google में मैच करता है / जो भी खोज करता है - शीर्षक पूछता है कि ये कैसे काम करते हैं? , न जाने कब रुकना है? । फिर भी, अंतर्निहित सवाल निहित है कुछ हद तक इस जवाब में शामिल है, और की तलाश में किसी और के लिए जवाब देने के लिए पर चला जाता है अन्य जवाब। सिंटैक्स प्रश्नों जैसे कि ये शीर्षक शीर्षक वाले होने चाहिए ताकि एक उत्तर को उस अकेले का उपयोग करके लिखा जा सके क्योंकि यह सभी जानकारी खोजकर्ता को प्रश्न खोजने की आवश्यकता है। आप निश्चित रूप से गलत नहीं हैं - सवाल का शीर्षक नहीं है जैसा कि यह होना चाहिए।
सुपर कैट

0

कुछ नमूना कोड ढेर पर सरणियों के बीच अंतर का प्रदर्शन करने के लिए ढेर पर


/**
 * Question: Can we use range based for built-in arrays
 * Answer: Maybe
 * 1) Yes, when array is on the Stack
 * 2) No, when array is the Heap
 * 3) Yes, When the array is on the Stack,
 *    but the array elements are on the HEAP
 */
void testStackHeapArrays() {
  int Size = 5;
  Square StackSquares[Size];  // 5 Square's on Stack
  int StackInts[Size];        // 5 int's on Stack
  // auto is Square, passed as constant reference
  for (const auto &Sq : StackSquares)
    cout << "StackSquare has length " << Sq.getLength() << endl;
  // auto is int, passed as constant reference
  // the int values are whatever is in memory!!!
  for (const auto &I : StackInts)
    cout << "StackInts value is " << I << endl;

  // Better version would be: auto HeapSquares = new Square[Size];
  Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's it has
  // for (auto &Sq : HeapSquares)
  //    cout << "HeapSquare has length " << Sq.getLength() << endl;

  // does not compile, same reason as above
  // for (const auto &I : HeapInts)
  //  cout << "HeapInts value is " << I << endl;

  // Create 3 Square objects on the Heap
  // Create an array of size-3 on the Stack with Square pointers
  // size of array is known to compiler
  Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
  // auto is Square*, passed as constant reference
  for (const auto &Sq : HeapSquares2)
    cout << "HeapSquare2 has length " << Sq->getLength() << endl;

  // Create 3 int objects on the Heap
  // Create an array of size-3 on the Stack with int pointers
  // size of array is known to compiler
  int *HeapInts2[]{new int(23), new int(57), new int(99)};
  // auto is int*, passed as constant reference
  for (const auto &I : HeapInts2)
    cout << "HeapInts2 has value " << *I << endl;

  delete[] HeapSquares;
  delete[] HeapInts;
  for (const auto &Sq : HeapSquares2) delete Sq;
  for (const auto &I : HeapInts2) delete I;
  // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.