PHP 'foreach' वास्तव में कैसे काम करता है?


2018

मुझे यह कहकर उपसर्ग करने दो कि मुझे पता है कि क्या foreachहै, क्या करता है और इसका उपयोग कैसे करना है। यह प्रश्न चिंता करता है कि यह बोनट के नीचे कैसे काम करता है, और मैं "यह नहीं है कि आप एक सरणी के साथ कैसे लूप" की तर्ज पर कोई उत्तर चाहते हैं foreach


लंबे समय तक मैंने यह माना कि foreachसरणी के साथ ही काम किया। तब मुझे इस तथ्य के कई संदर्भ मिले कि यह सरणी की एक प्रति के साथ काम करता है , और मैंने तब से इसे कहानी का अंत माना है। लेकिन मैं हाल ही में इस मामले पर चर्चा में आया, और थोड़े प्रयोग के बाद पाया कि यह वास्तव में 100% सच नहीं था।

मुझे दिखाओ कि मेरा क्या मतलब है। निम्नलिखित परीक्षण मामलों के लिए, हम निम्नलिखित सरणी के साथ काम करेंगे:

$array = array(1, 2, 3, 4, 5);

टेस्ट केस 1 :

foreach ($array as $item) {
  echo "$item\n";
  $array[] = $item;
}
print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 2 3 4 5 1 2 3 4 5 */

यह स्पष्ट रूप से दिखाता है कि हम सीधे स्रोत सरणी के साथ काम नहीं कर रहे हैं - अन्यथा लूप हमेशा के लिए जारी रहेगा, क्योंकि हम लूप के दौरान सरणी पर लगातार वस्तुओं को धक्का दे रहे हैं। लेकिन यह सुनिश्चित करने के लिए कि यह मामला है:

टेस्ट केस 2 :

foreach ($array as $key => $item) {
  $array[$key + 1] = $item + 2;
  echo "$item\n";
}

print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 3 4 5 6 7 */

यह हमारे प्रारंभिक निष्कर्ष का समर्थन करता है, हम लूप के दौरान स्रोत सरणी की एक प्रति के साथ काम कर रहे हैं, अन्यथा हम लूप के दौरान संशोधित मूल्यों को देखेंगे। परंतु...

यदि हम मैनुअल में देखते हैं, तो हम यह कथन पाते हैं:

जब फॉर्चे पहले निष्पादित करना शुरू करते हैं, तो आंतरिक सरणी सूचक स्वचालित रूप से सरणी के पहले तत्व पर रीसेट हो जाता है।

सही ... यह सुझाव देता है कि foreachस्रोत सरणी के सूचक पर निर्भर करता है। लेकिन हमने अभी साबित किया है कि हम स्रोत सरणी के साथ काम नहीं कर रहे हैं , है ना? खैर, पूरी तरह से नहीं।

टेस्ट केस 3 :

// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));

foreach ($array as $item) {
  echo "$item\n";
}

var_dump(each($array));

/* Output
  array(4) {
    [1]=>
    int(1)
    ["value"]=>
    int(1)
    [0]=>
    int(0)
    ["key"]=>
    int(0)
  }
  1
  2
  3
  4
  5
  bool(false)
*/

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

PHP मैनुअल भी बताता है:

के रूप में foreach आंतरिक सरणी सूचक पर निर्भर करता है इसे लूप के भीतर बदलकर अप्रत्याशित व्यवहार हो सकता है।

ठीक है, आइए जानें कि "अप्रत्याशित व्यवहार" क्या है (तकनीकी रूप से, कोई भी व्यवहार अप्रत्याशित है क्योंकि मैं अब नहीं जानता कि क्या उम्मीद की जाए)।

टेस्ट केस 4 :

foreach ($array as $key => $item) {
  echo "$item\n";
  each($array);
}

/* Output: 1 2 3 4 5 */

टेस्ट केस 5 :

foreach ($array as $key => $item) {
  echo "$item\n";
  reset($array);
}

/* Output: 1 2 3 4 5 */

... कुछ भी नहीं है कि वहाँ अप्रत्याशित, वास्तव में यह "स्रोत की प्रतिलिपि" सिद्धांत का समर्थन करने लगता है।


प्रश्न

यहाँ क्या हो रहा है? मेरा सी-फू मेरे लिए इतना अच्छा नहीं है कि मैं PHP स्रोत कोड को देखकर एक उचित निष्कर्ष निकाल सकूं, अगर कोई मेरे लिए इसे अंग्रेजी में अनुवाद कर सकता है तो मैं इसकी सराहना करूंगा।

यह मुझे लगता है कि सरणी की foreachएक प्रति के साथ काम करता है , लेकिन लूप के बाद सरणी के अंत तक स्रोत सरणी के सरणी सूचक को सेट करता है।

  • क्या यह सही है और पूरी कहानी है?
  • यदि नहीं, तो यह वास्तव में क्या कर रहा है?
  • क्या कोई ऐसी स्थिति है जहां फंक्शन पॉइंटर (और each(), reset()एट अल।) को समायोजित करने वाले फ़ंक्शंस का उपयोग foreachकरना लूप के परिणाम को प्रभावित कर सकता है?

5
@DaveRandom एक php-internals टैग है, जिसे शायद इसके साथ जाना चाहिए, लेकिन मैं इसे आपको यह तय करने के लिए छोड़ दूँगा कि यदि अन्य 5 टैग को बदलना है।
माइकल बर्कोव्स्की

5
गाय की तरह दिखता है, हटाए बिना हैंडल
zb '

149
पहले मैंने सोचा था कि ", एक और नौसिखिया सवाल। डॉक्स पढ़ें ... एचएम, स्पष्ट रूप से अपरिभाषित व्यवहार «। तब मैंने पूरा प्रश्न पढ़ा, और मुझे कहना चाहिए: मुझे यह पसंद है। आपने इसमें काफी प्रयास किए हैं और सभी टेस्टकेस को लिखा है। ps। टेस्टकेस 4 और 5 समान हैं?
knittl

21
बस इस बारे में एक विचार कि यह क्यों समझ में आता है कि सरणी सूचक स्पर्श हो जाता है: PHP को प्रतिलिपि के साथ मूल सरणी के आंतरिक सरणी सूचक को रीसेट करने और स्थानांतरित करने की आवश्यकता होती है, क्योंकि उपयोगकर्ता वर्तमान मूल्य के संदर्भ में पूछ सकता है ( foreach ($array as &$value)) - PHP को मूल सरणी में वर्तमान स्थिति जानने की आवश्यकता है, भले ही यह वास्तव में एक प्रतिलिपि पर पुनरावृत्ति हो।
निको

4
@ सीन: IMHO, मूल भाषा सुविधाओं की बारीकियों का वर्णन करने में PHP प्रलेखन वास्तव में काफी खराब है। लेकिन यह है, शायद, क्योंकि इतने सारे तदर्थ विशेष मामलों भाषा में पके हुए हैं ...
ओलिवर चार्ल्सवर्थ

जवाबों:


1660

foreach मूल्यों के तीन अलग-अलग प्रकारों पर चलना का समर्थन करता है:

  • Arrays
  • सामान्य वस्तु
  • Traversable वस्तुओं

निम्नलिखित में, मैं ठीक से समझाने की कोशिश करूंगा कि विभिन्न मामलों में पुनरावृत्ति कैसे काम करती है। अब तक सबसे सरल मामला Traversableवस्तुओं का है, क्योंकि foreachइन पंक्तियों के लिए कोड के लिए अनिवार्य रूप से केवल सिंटेक्स चीनी है:

foreach ($it as $k => $v) { /* ... */ }

/* translates to: */

if ($it instanceof IteratorAggregate) {
    $it = $it->getIterator();
}
for ($it->rewind(); $it->valid(); $it->next()) {
    $v = $it->current();
    $k = $it->key();
    /* ... */
}

आंतरिक कक्षाओं के लिए, वास्तविक एपीआई को आंतरिक एपीआई का उपयोग करके टाल दिया जाता है जो अनिवार्य रूप Iteratorसे सी स्तर पर इंटरफ़ेस को प्रतिबिंबित करता है।

सरणियों और सादे वस्तुओं का वर्गीकरण काफी अधिक जटिल है। सबसे पहले, यह ध्यान दिया जाना चाहिए कि PHP में "सरणियों" को वास्तव में शब्दकोशों का आदेश दिया गया है और उन्हें इस आदेश के अनुसार ट्रेस किया जाएगा (जो सम्मिलन क्रम से मेल खाती है जब तक कि आप कुछ का उपयोग नहीं करते sort)। यह कुंजियों के प्राकृतिक क्रम (अन्य भाषाओं में सूचियाँ कैसे काम करती हैं) या इसमें कोई परिभाषित क्रम नहीं होने के कारण इसका विरोध किया जाता है (कैसे अन्य भाषाओं में शब्द अक्सर काम करते हैं)।

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

अब तक सब ठीक है। एक शब्द से अधिक उच्चारण करना बहुत कठिन नहीं है, है ना? समस्याएं तब शुरू होती हैं जब आपको पता चलता है कि पुनरावृत्ति के दौरान एक सरणी / वस्तु बदल सकती है। इसके कई तरीके हो सकते हैं:

  • यदि आप संदर्भ का उपयोग करके पुनरावृति करते हैं foreach ($arr as &$v)तो संदर्भ $arrमें बदल जाता है और आप इसे पुनरावृत्ति के दौरान बदल सकते हैं।
  • PHP 5 में भी यही बात लागू होती है, भले ही आप मूल्य से पुनरावृति करते हों, लेकिन सरणी पहले से एक संदर्भ थी: $ref =& $arr; foreach ($ref as $v)
  • वस्तुओं के पास पासिंग शब्दार्थ होते हैं, जो अधिकांश व्यावहारिक उद्देश्यों के लिए इसका मतलब है कि वे संदर्भों की तरह व्यवहार करते हैं। इसलिए वस्तुओं को हमेशा पुनरावृत्ति के दौरान बदला जा सकता है।

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

इस मुद्दे को हल करने के विभिन्न तरीके हैं। PHP 5 और PHP 7 इस संबंध में काफी भिन्न हैं और मैं निम्नलिखित में दोनों व्यवहारों का वर्णन करूँगा। सारांश यह है कि PHP 5 का दृष्टिकोण गूंगा था और सभी प्रकार के अजीब धार वाले मुद्दों के लिए नेतृत्व करता था, जबकि PHP 7 के अधिक शामिल दृष्टिकोण से अधिक पूर्वानुमान और सुसंगत व्यवहार होता है।

अंतिम प्रारंभिक के रूप में, यह ध्यान दिया जाना चाहिए कि PHP संदर्भ गणना और मेमोरी को प्रबंधित करने के लिए कॉपी-ऑन-राइट का उपयोग करता है। इसका मतलब यह है कि यदि आप किसी मूल्य को "कॉपी" करते हैं, तो आप वास्तव में पुराने मूल्य का पुन: उपयोग करते हैं और इसकी संदर्भ संख्या (प्रतिफल) बढ़ाते हैं। केवल एक बार जब आप किसी प्रकार का संशोधन करते हैं तो एक वास्तविक प्रतिलिपि (जिसे "दोहराव" कहा जाता है) हो जाएगी। देखें कि आप इस विषय पर अधिक व्यापक परिचय के लिए झूठ बोले जा रहे हैं।

PHP 5

आंतरिक सरणी सूचक और हैशपॉइंट

PHP 5 में एरर्स में एक समर्पित "आंतरिक सरणी पॉइंटर" (IAP) है, जो संशोधनों का ठीक से समर्थन करता है: जब भी कोई तत्व हटा दिया जाता है, तो एक जाँच होगी कि क्या IAP इस तत्व को इंगित करता है। यदि ऐसा होता है, तो इसके बजाय अगले तत्व के लिए उन्नत है।

foreachआईएपी का उपयोग करते समय , एक अतिरिक्त जटिलता है: केवल एक आईएपी है, लेकिन एक सरणी कई foreachलूप का हिस्सा हो सकती है:

// Using by-ref iteration here to make sure that it's really
// the same array in both loops and not a copy
foreach ($arr as &$v1) {
    foreach ($arr as &$v) {
        // ...
    }
}

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

एरे डुप्लीकेशन

IAP एक सरणी ( currentफ़ंक्शन के परिवार के माध्यम से उजागर ) की एक दृश्यमान विशेषता है , जैसे कि IAP की गिनती कॉपी-ऑन-राइट सेमेंटिक्स के तहत संशोधनों के रूप में होती है। यह, दुर्भाग्य से, इसका मतलब यह foreachहै कि कई मामलों में यह सरणी को डुप्लिकेट करने के लिए मजबूर किया जाता है। सटीक शर्तें हैं:

  1. सरणी एक संदर्भ नहीं है (is_ref = 0)। यदि यह एक संदर्भ है, तो बदल जाता है करने के लिए यह कर रहे हैं चाहिए प्रचार करने के लिए, तो यह दोहराया नहीं जाना चाहिए।
  2. सरणी में refcount> 1 है। यदि refcount1 है, तो सरणी साझा नहीं की जाती है और हम इसे सीधे संशोधित करने के लिए स्वतंत्र हैं।

यदि सरणी को डुप्लिकेट नहीं किया गया है (is_ref = 0, refcount = 1), तो केवल इसका refcountवेतन वृद्धि (*) होगी। इसके अतिरिक्त, यदि foreachसंदर्भ द्वारा उपयोग किया जाता है, तो (संभावित डुप्लिकेट) सरणी को संदर्भ में बदल दिया जाएगा।

इस कोड को एक उदाहरण के रूप में देखें जहां दोहराव होता है:

function iterate($arr) {
    foreach ($arr as $v) {}
}

$outerArr = [0, 1, 2, 3, 4];
iterate($outerArr);

यहां, $arrIAP परिवर्तनों को $arrलीक होने से रोकने के लिए डुप्लिकेट किया जाएगा $outerArr। उपरोक्त शर्तों के संदर्भ में, सरणी एक संदर्भ नहीं है (is_ref = 0) और इसका उपयोग दो स्थानों पर किया जाता है (refcount 2)। यह आवश्यकता दुर्भाग्यपूर्ण है और उप-अपनाने के कार्यान्वयन की एक कलाकृति है (यहाँ पुनरावृत्ति के दौरान संशोधन की कोई चिंता नहीं है, इसलिए हमें वास्तव में पहली जगह में IAP का उपयोग करने की आवश्यकता नहीं है)।

(*) refcountयहाँ बढ़ाना अहानिकर लगता है, लेकिन कॉपी-ऑन-राइट (COW) शब्दार्थ का उल्लंघन करता है: इसका मतलब है कि हम एक refcount = 2 सरणी के IAP को संशोधित करने जा रहे हैं, जबकि गाय का कहना है कि संशोधन केवल refcount = पर किए जा सकते हैं 1 मान। यह उल्लंघन उपयोगकर्ता-दृश्यमान व्यवहार परिवर्तन (जबकि एक सामान्य रूप से पारदर्शी है) में परिणाम होता है क्योंकि पुनरावृत्त सरणी पर IAP परिवर्तन देखने योग्य होगा - लेकिन केवल सरणी पर पहले गैर-IAP संशोधन तक। इसके बजाय, तीन "वैध" विकल्प हमेशा a (डुप्लिकेट) होते, b) वृद्धि नहीं करते हैं refcountऔर इस प्रकार पुनरावृत्त सरणी को मनमाने ढंग से लूप या c में संशोधित करने की अनुमति देते हैं) IAP का उपयोग बिल्कुल भी न करें (PHP) 7 समाधान)।

स्थिति उन्नति क्रम

एक अंतिम कार्यान्वयन विवरण है जिसे आपको नीचे दिए गए कोड नमूनों को ठीक से समझने के लिए जागरूक होना होगा। कुछ डेटा संरचना के माध्यम से लूपिंग का "सामान्य" तरीका छद्मकोड में कुछ इस तरह दिखाई देगा:

reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
    code();
    move_forward(arr);
}

हालांकि foreach, बल्कि एक विशेष हिमपात का एक खंड होने के नाते, चीजों को थोड़ा अलग तरीके से करने का विकल्प चुनता है:

reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
    move_forward(arr);
    code();
}

स्वचालित रूप से, सरणी सूचक लूप बॉडी के चलने से पहले ही आगे बढ़ जाता है । इसका मतलब यह है कि जब लूप बॉडी तत्व पर काम कर रहा है $i, तो IAP पहले से ही तत्व में है $i+1। यही वजह है कि कोड नमूने यात्रा के दौरान संशोधन दिखा है हमेशा अगले तत्व के बजाय वर्तमान एक।unset

उदाहरण: आपके परीक्षण के मामले

ऊपर वर्णित तीन पहलुओं को आपको foreachकार्यान्वयन की अज्ञातताओं के बारे में पूरी तरह से छाप देना चाहिए और हम कुछ उदाहरणों पर चर्चा करने के लिए आगे बढ़ सकते हैं।

इस बिंदु पर समझाने के लिए आपके परीक्षण मामलों का व्यवहार सरल है:

  • परीक्षण के मामलों में 1 और 2 $arrayप्रतिफल = 1 से शुरू होता है, इसलिए इसे इसके द्वारा दोहराया नहीं जाएगा foreach: केवल refcountवृद्धि होती है। जब लूप बॉडी बाद में सरणी को संशोधित करता है (जिसमें उस बिंदु पर Refcount = 2 है), तो उस बिंदु पर दोहराव होगा। Foreach की अनमॉडिफाइड कॉपी पर काम करना जारी रखेगा $array

  • टेस्ट केस 3 में, एक बार फिर एरे को डुप्लिकेट नहीं किया जाता है, इस प्रकार foreachयह $arrayवेरिएबल के IAP को संशोधित करेगा । पुनरावृत्ति के अंत में, IAP NULL है (इसका अर्थ है कि पुनरावृत्ति ने किया है), जो eachकि लौटने से इंगित करता है false

  • परीक्षण के मामलों में 4 और 5 दोनों हैं eachऔर resetउप-संदर्भ फ़ंक्शन हैं। $arrayएक है refcount=2जब यह, उन से पारित हो जाता है तो यह दोहराया जाना चाहिए। जैसे कि foreachएक अलग सरणी पर फिर से काम करना होगा।

उदाहरण: currentफोर्क में प्रभाव

विभिन्न दोहराव व्यवहारों को दिखाने का एक अच्छा तरीका current()एक foreachलूप के अंदर फ़ंक्शन के व्यवहार का निरीक्षण करना है । इस उदाहरण पर विचार करें:

foreach ($array as $val) {
    var_dump(current($array));
}
/* Output: 2 2 2 2 2 */

यहां आपको पता होना चाहिए कि current()यह एक बाय-रिफ फ़ंक्शन है (वास्तव में: पसंद-रेफ), भले ही यह सरणी को संशोधित नहीं करता है। यह अन्य सभी कार्यों के साथ अच्छा खेलने के लिए होना चाहिए, जैसे nextकि सभी उप-रेफ। बाय-रेफरेंस पासिंग का मतलब है कि एरे को अलग करना होगा और इस तरह $arrayऔर foreach-arrayवसीयत अलग होगी। इसके 2बजाय आपको जो कारण मिलता है 1वह भी ऊपर उल्लेखित है: उपयोगकर्ता कोड को चलाने से पहलेforeach सरणी सूचक को आगे बढ़ाता है , बाद में नहीं। तो भले ही कोड पहले तत्व पर है, पहले से ही सूचक को दूसरे में उन्नत किया।foreach

अब एक छोटे से संशोधन की कोशिश करते हैं:

$ref = &$array;
foreach ($array as $val) {
    var_dump(current($array));
}
/* Output: 2 3 4 5 false */

यहां हमारे पास is_ref = 1 मामला है, इसलिए सरणी की प्रतिलिपि नहीं बनाई गई है (बस ऊपर की तरह)। लेकिन अब जब यह एक संदर्भ है, तो सरणी को उप-रेफ current()फ़ंक्शन में पास होने पर दोहराया नहीं जाना है । इस प्रकार current()और foreachउसी सरणी पर काम करते हैं। आप अभी भी ऑफ-द-वन व्यवहार देखते हैं, हालांकि, foreachपॉइंटर को आगे बढ़ाने के तरीके के कारण ।

पुनरावृत्ति करते समय आपको समान व्यवहार मिलता है:

foreach ($array as &$val) {
    var_dump(current($array));
}
/* Output: 2 3 4 5 false */

यहां महत्वपूर्ण हिस्सा यह है कि $arrayजब यह संदर्भ द्वारा पुनरावृत्त होता है, तो foreach is_ref = 1 बना देगा , इसलिए मूल रूप से आपके पास ऊपर जैसी स्थिति है।

एक और छोटा बदलाव, इस बार हम दूसरे वेरिएबल में ऐरे को असाइन करेंगे:

$foo = $array;
foreach ($array as $val) {
    var_dump(current($array));
}
/* Output: 1 1 1 1 1 */

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

उदाहरण: पुनरावृत्ति के दौरान संशोधन

पुनरावृत्ति के दौरान संशोधनों के लिए खाते में लाने की कोशिश करना हमारी सभी परेशानियों की उत्पत्ति है, इसलिए यह इस मामले के लिए कुछ उदाहरणों पर विचार करता है।

एक ही सरणी पर इन नीडिंत छोरों पर विचार करें (जहां बाय-रेफ पुनरावृत्ति का उपयोग यह सुनिश्चित करने के लिए किया जाता है कि यह वास्तव में एक ही है):

foreach ($array as &$v1) {
    foreach ($array as &$v2) {
        if ($v1 == 1 && $v2 == 1) {
            unset($array[1]);
        }
        echo "($v1, $v2)\n";
    }
}

// Output: (1, 1) (1, 3) (1, 4) (1, 5)

यहां अपेक्षित भाग (1, 2)आउटपुट से गायब है क्योंकि तत्व 1को हटा दिया गया था। संभवतः यह अप्रत्याशित है कि पहले तत्व के बाद बाहरी लूप बंद हो जाता है। ऐसा क्यों है?

इसके पीछे का कारण ऊपर वर्णित नेस्टेड-लूप हैक है: लूप बॉडी के चलने से पहले, वर्तमान IAP स्थिति और हैश का समर्थन किया जाता है HashPointer। लूप बॉडी के बाद इसे बहाल किया जाएगा, लेकिन केवल अगर तत्व अभी भी मौजूद है, अन्यथा इसके बजाय वर्तमान IAP स्थिति (जो भी हो सकती है) का उपयोग किया जाता है। ऊपर दिए गए उदाहरण में ठीक यही स्थिति है: बाहरी लूप का वर्तमान तत्व हटा दिया गया है, इसलिए यह IAP का उपयोग करेगा, जिसे पहले से ही आंतरिक लूप द्वारा समाप्त के रूप में चिह्नित किया गया है!

HashPointerबैकअप + पुनर्स्थापना तंत्र का एक और परिणाम यह है कि आईएपी के माध्यम से परिवर्तन reset()आदि आमतौर पर प्रभावित नहीं करते हैं foreach। उदाहरण के लिए, निम्न कोड निष्पादित करता है जैसे कि reset()बिल्कुल मौजूद नहीं थे:

$array = [1, 2, 3, 4, 5];
foreach ($array as &$value) {
    var_dump($value);
    reset($array);
}
// output: 1, 2, 3, 4, 5

कारण यह है कि, reset()अस्थायी रूप से IAP को संशोधित करते हुए , यह लूप बॉडी के बाद वर्तमान फ़ॉरच्यू तत्व में पुनर्स्थापित किया जाएगा। reset()लूप पर एक प्रभाव बनाने के लिए मजबूर करने के लिए, आपको अतिरिक्त रूप से वर्तमान तत्व को निकालना होगा, ताकि बैकअप / पुनर्स्थापना तंत्र विफल हो जाए:

$array = [1, 2, 3, 4, 5];
$ref =& $array;
foreach ($array as $value) {
    var_dump($value);
    unset($array[1]);
    reset($array);
}
// output: 1, 1, 3, 4, 5

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

$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
$ref =& $array;
foreach ($array as $value) {
    unset($array['EzFY']);
    $array['FYFY'] = 4;
    reset($array);
    var_dump($value);
}
// output: 1, 4

यहां हमें सामान्य रूप 1, 1, 3, 4से पिछले नियमों के अनुसार आउटपुट की उम्मीद करनी चाहिए । कैसे होता है कि 'FYFY'हटाए गए तत्व के समान हैश होता है 'EzFY', और आवंटनकर्ता तत्व को संग्रहीत करने के लिए समान मेमोरी स्थान का पुन: उपयोग करने के लिए होता है। इसलिए फॉर्च्यूस सीधे नए सम्मिलित तत्व पर कूदता है, इस प्रकार लूप को शॉर्ट-कट करता है।

लूप के दौरान पुनरावृत्त इकाई को प्रतिस्थापित करना

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

$arr = [1, 2, 3, 4, 5];
$obj = (object) [6, 7, 8, 9, 10];

$ref =& $arr;
foreach ($ref as $val) {
    echo "$val\n";
    if ($val == 3) {
        $ref = $obj;
    }
}
/* Output: 1 2 3 6 7 8 9 10 */

जैसा कि आप इस मामले में देख सकते हैं कि एक बार प्रतिस्थापन होने के बाद ही PHP दूसरी इकाई को शुरू करना शुरू कर देगी।

PHP 7

हैशटेबल पुनरावृत्तियों

यदि आपको अभी भी याद है, तो सरणी पुनरावृत्ति के साथ मुख्य समस्या यह थी कि तत्वों को हटाने के लिए मध्य-पुनरावृत्ति को कैसे प्रबंधित किया जाए। PHP 5 ने इस उद्देश्य के लिए एक एकल आंतरिक सरणी पॉइंटर (IAP) का उपयोग किया, जो कुछ हद तक उप-रूपी था, क्योंकि एक सरणी पॉइंटर को एक साथ कई फ़ॉरच्युन लूप और उसके साथ बातचीत reset()आदि का समर्थन करने के लिए बढ़ाया जाना था ।

PHP 7 एक अलग दृष्टिकोण का उपयोग करता है, अर्थात्, यह बाहरी, सुरक्षित हैशटेबल पुनरावृत्तियों की एक मनमानी राशि बनाने का समर्थन करता है। इन पुनरावृत्तियों को सरणी में पंजीकृत किया जाना है, जिस बिंदु से उनके पास आईएपी के समान शब्दार्थ है: यदि कोई सरणी तत्व हटा दिया जाता है, तो उस तत्व की ओर इशारा करने वाले सभी हैशटेबल पुनरावृत्त अगले तत्व के लिए उन्नत होंगे।

इसका मतलब है कि foreachअब IAP का उपयोग बिल्कुल नहीं किया जाएगाforeachपाश के परिणामों पर बिल्कुल कोई असर होगा current()आदि और अपने स्वयं के व्यवहार जैसे कार्यों से कभी नहीं प्रभावित होगा reset()आदि

एरे डुप्लीकेशन

PHP 5 और PHP 7 के बीच एक और महत्वपूर्ण बदलाव सरणी दोहराव से संबंधित है। अब जब IAP का उपयोग नहीं किया जाता है, तो refcountसभी मामलों में बाय-वैल्यू एरेशन केवल एक वृद्धि (डुप्लिकेट एरे की जगह) ही करेगा । यदि foreachलूप के दौरान सरणी को संशोधित किया गया है, तो उस बिंदु पर एक दोहराव घटित होगा (कॉपी-ऑन-राइट के अनुसार) और foreachपुराने सरणी पर काम करना जारी रखेगा।

ज्यादातर मामलों में, यह परिवर्तन पारदर्शी है और बेहतर प्रदर्शन के अलावा इसका कोई अन्य प्रभाव नहीं है। हालाँकि, एक ऐसा अवसर होता है, जहाँ इसका परिणाम भिन्न व्यवहार के रूप में होता है, अर्थात् जहाँ मामला पहले एक संदर्भ था:

$array = [1, 2, 3, 4, 5];
$ref = &$array;
foreach ($array as $val) {
    var_dump($val);
    $array[2] = 0;
}
/* Old output: 1, 2, 0, 4, 5 */
/* New output: 1, 2, 3, 4, 5 */

पूर्व में संदर्भ-सरणियों का मूल्य-निर्धारण पुनरावृत्ति विशेष मामले थे। इस मामले में, कोई दोहराव नहीं हुआ, इसलिए पुनरावृति के दौरान सरणी के सभी संशोधन लूप द्वारा परिलक्षित होंगे। PHP 7 में यह विशेष मामला चला गया है: किसी सरणी का बाय-वैल्यू चलना हमेशा मूल तत्वों पर काम करता रहेगा , लूप के दौरान किसी भी संशोधन की उपेक्षा करता है।

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

$obj = new stdClass;
$obj->foo = 1;
$obj->bar = 2;
foreach ($obj as $val) {
    var_dump($val);
    $obj->bar = 42;
}
/* Old and new output: 1, 42 */

यह वस्तुओं के उप-संभाल शब्दार्थ को दर्शाता है (यानी वे संदर्भ-जैसे व्यवहार को भी-मूल्य संदर्भों में दर्शाते हैं)।

उदाहरण

आइए कुछ उदाहरणों पर विचार करें, जो आपके परीक्षण मामलों से शुरू होते हैं:

  • टेस्ट केस 1 और 2 एक ही आउटपुट को बनाए रखते हैं: बाय-वैल्यू ऐरे इट्रेशन हमेशा मूल तत्वों पर काम करते रहते हैं। (इस मामले में, यहां तक ​​कि refcountingऔर दोहराव का व्यवहार PHP 5 और PHP 7 के बीच बिल्कुल समान है)।

  • टेस्ट केस 3 में बदलाव: Foreachआईएपी का उपयोग नहीं करता है, इसलिए each()लूप से प्रभावित नहीं होता है। यह पहले और बाद में एक ही आउटपुट होगा।

  • परीक्षण के मामले 4 और 5 समान रहते हैं: each()और reset()IAP को बदलने से पहले सरणी को डुप्लिकेट करेगा, जबकि foreachअभी भी मूल सरणी का उपयोग करता है। (ऐसा नहीं है कि IAP परिवर्तन मायने रखता था, भले ही सरणी साझा की गई थी।)

उदाहरणों का दूसरा सेट current()विभिन्न reference/refcountingविन्यासों के तहत व्यवहार से संबंधित था । यह अब समझ में नहीं आता है, जैसा current()कि लूप द्वारा पूरी तरह से अप्रभावित है, इसलिए इसका वापसी मूल्य हमेशा एक जैसा रहता है।

हालाँकि, पुनरावृत्ति के दौरान संशोधनों पर विचार करने पर हमें कुछ दिलचस्प बदलाव मिलते हैं। मुझे आशा है कि आप नए व्यवहार को पा लेंगे। पहला उदाहरण:

$array = [1, 2, 3, 4, 5];
foreach ($array as &$v1) {
    foreach ($array as &$v2) {
        if ($v1 == 1 && $v2 == 1) {
            unset($array[1]);
        }
        echo "($v1, $v2)\n";
    }
}

// Old output: (1, 1) (1, 3) (1, 4) (1, 5)
// New output: (1, 1) (1, 3) (1, 4) (1, 5)
//             (3, 1) (3, 3) (3, 4) (3, 5)
//             (4, 1) (4, 3) (4, 4) (4, 5)
//             (5, 1) (5, 3) (5, 4) (5, 5) 

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

एक और अजीब बढ़त का मामला जो अब तय हो गया है, जब आप हटाते हैं और एक ही हैश के साथ होने वाले तत्वों को जोड़ते हैं तो आपको मिलने वाला अजीब प्रभाव होता है:

$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
foreach ($array as &$value) {
    unset($array['EzFY']);
    $array['FYFY'] = 4;
    var_dump($value);
}
// Old output: 1, 4
// New output: 1, 3, 4

पहले HashPointer पुनर्स्थापना तंत्र ने नए तत्व के लिए सही छलांग लगाई क्योंकि यह "देखा" था जैसे कि हटाए गए तत्व (टकराने वाले हैश और पॉइंटर के कारण) के समान है। जैसा कि अब हम किसी चीज़ के लिए तत्व हैश पर निर्भर नहीं हैं, यह अब एक मुद्दा नहीं है।


4
@ बाबा यह करता है। इसे फंक्शन में पास करना $foo = $arrayलूप से पहले करने जैसा है ;)
NikiC

32
आप में से जो लोग क्या एक zval है पता नहीं है के लिए, करने के लिए सारा गोलमैन के संदर्भ लें blog.golemon.com/2007/01/youre-being-lied-to.html
शू ZOMG chen

1
मामूली सुधार: जिसे आप बाल्टी कहते हैं, वह हैशटेबल में आमतौर पर बाल्टी नहीं कहा जाता है। आम तौर पर बाल्टी उसी हैश% आकार के साथ प्रविष्टियों का एक सेट है। आप इसे आम तौर पर एक प्रविष्टि कहा जाता है के लिए उपयोग करने लगते हैं। लिंक की गई सूची बाल्टियों पर नहीं है, बल्कि प्रविष्टियों पर है।
unbeli

12
@unbeli मैं PHP द्वारा आंतरिक रूप से उपयोग की जाने वाली शब्दावली का उपयोग कर रहा हूं। Bucketरों हैश टकराव के लिए एक दोगुना लिंक्ड सूची है और यह भी आदेश के लिए एक दोगुना लिंक्ड सूची का हिस्सा का हिस्सा हैं;)
NikiC

4
महान अन्वेषक। मुझे लगता है कि आपका मतलब है iterate($outerArr);और iterate($arr);कहीं नहीं है।
नियाउ

116

उदाहरण 3 में आप सरणी को संशोधित नहीं करते हैं। अन्य सभी उदाहरणों में आप या तो सामग्री या आंतरिक सरणी सूचक को संशोधित करते हैं। असाइनमेंट ऑपरेटर के शब्दार्थ के कारण PHP एरेज़ की बात आती है तो यह महत्वपूर्ण है ।

PHP में सरणियों के लिए असाइनमेंट ऑपरेटर एक आलसी क्लोन की तरह अधिक काम करता है। एक चर को दूसरे में निर्दिष्ट करना जिसमें एक सरणी होती है, अधिकांश भाषाओं के विपरीत, सरणी को क्लोन करेगा। हालांकि, वास्तविक क्लोनिंग तब तक नहीं की जाएगी जब तक कि इसकी आवश्यकता न हो। इसका मतलब यह है कि क्लोन तभी होगा जब कोई भी चर संशोधित (कॉपी-ऑन-राइट) हो।

यहाँ एक उदाहरण है:

$a = array(1,2,3);
$b = $a;  // This is lazy cloning of $a. For the time
          // being $a and $b point to the same internal
          // data structure.

$a[] = 3; // Here $a changes, which triggers the actual
          // cloning. From now on, $a and $b are two
          // different data structures. The same would
          // happen if there were a change in $b.

अपने परीक्षण मामलों में वापस आकर, आप आसानी से कल्पना कर सकते हैं कि foreachसरणी के संदर्भ में किसी प्रकार का पुनरावृत्ति बनाता है। यह संदर्भ $bमेरे उदाहरण में चर की तरह ही काम करता है । हालाँकि, पुनरावृत्ति के साथ पुनरावृत्ति लूप के दौरान ही रहते हैं और फिर, वे दोनों खारिज कर दिए जाते हैं। अब आप देख सकते हैं कि, सभी मामलों में लेकिन 3, लूप के दौरान सरणी को संशोधित किया गया है, जबकि यह अतिरिक्त संदर्भ जीवित है। यह एक क्लोन को ट्रिगर करता है, और यह बताता है कि यहाँ क्या हो रहा है!

इस कॉपी-ऑन-राइट व्यवहार के एक अन्य पक्ष प्रभाव के लिए एक उत्कृष्ट लेख है: PHP टर्नरी ऑपरेटर: फास्ट या नहीं?


आपका अधिकार लगता है, मैंने कुछ उदाहरण बनाए जो यह प्रदर्शित करते हैं कि: codepad.org/OCjtvu8r आपके उदाहरण से एक अंतर है - यह कॉपी नहीं करता है यदि आप मान बदलते हैं, तो केवल चाबियाँ बदलें।
zb '

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

49

कुछ बिंदुओं पर ध्यान दें जब साथ काम करें foreach():

क) मूल सरणी foreachकी संभावित प्रतिलिपि पर काम करता है । इसका अर्थ है कि foreach()जब तक या जब तक फ़ॉरेस्ट नोट्स / उपयोगकर्ता टिप्पणीprospected copy नहीं बनाई जाती है, तब तक डेटा संग्रहण साझा किया जाएगा ।

ख) एक संभावित प्रतिलिपि को क्या ट्रिगर करता है ? एक संभावित प्रति बनाई गई है copy-on-write, जिसकी नीति के आधार पर , जब भी किसी सरणी foreach()को परिवर्तित किया जाता है, तो मूल सरणी का एक क्लोन बनाया जाता है।

ग) मूल सरणी और foreach()इट्रेटर DISTINCT SENTINEL VARIABLESमें मूल सरणी और अन्य के लिए एक होगा foreach; नीचे परीक्षण कोड देखें। एसपीएल , Iterators और ऐरे Iterator

स्टैक ओवरफ्लो प्रश्न यह सुनिश्चित करने के लिए कि PHP में मूल्य 'फॉर्च्यूनर' लूप में कैसे रीसेट किया जाता है? आपके प्रश्न के मामलों (3,4,5) को संबोधित करता है।

निम्नलिखित उदाहरण से पता चलता है कि प्रत्येक () और रीसेट () पुनरावृत्ति के SENTINELचर (for example, the current index variable)को प्रभावित नहीं करता foreach()है।

$array = array(1, 2, 3, 4, 5);

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

foreach($array as $key => $val){
    echo "foreach: $key => $val<br/>";

    list($key2,$val2) = each($array);
    echo "each() Original(inside): $key2 => $val2<br/>";

    echo "--------Iteration--------<br/>";
    if ($key == 3){
        echo "Resetting original array pointer<br/>";
        reset($array);
    }
}

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

आउटपुट:

each() Original (outside): 0 => 1
foreach: 0 => 1
each() Original(inside): 1 => 2
--------Iteration--------
foreach: 1 => 2
each() Original(inside): 2 => 3
--------Iteration--------
foreach: 2 => 3
each() Original(inside): 3 => 4
--------Iteration--------
foreach: 3 => 4
each() Original(inside): 4 => 5
--------Iteration--------
Resetting original array pointer
foreach: 4 => 5
each() Original(inside): 0=>1
--------Iteration--------
each() Original (outside): 1 => 2

2
आपका उत्तर बिल्कुल सही नहीं है। foreachसरणी की एक संभावित प्रतिलिपि पर काम करता है, लेकिन यह वास्तविक प्रतिलिपि तब तक नहीं बनाता है जब तक कि इसकी आवश्यकता न हो।
लाइनपोग्ल

क्या आप यह प्रदर्शित करना चाहेंगे कि कोड के माध्यम से कैसे और कब संभावित कॉपी बनाई जाए? मेरा कोड दर्शाता है कि foreachसमय के 100% सरणी को कॉपी कर रहा है। मैं जानने के लिए उत्सुक हूं। टिप्पणी के लिए धन्यवाद
sakhunzai

एक सरणी को कॉपी करने में बहुत खर्च होता है। forया तो का उपयोग करके 100000 तत्वों के साथ एक सरणी को पुनरावृत्त करने में लगने वाले समय को गिनने का प्रयास करें foreach। आप उन दोनों के बीच कोई महत्वपूर्ण अंतर नहीं देखेंगे, क्योंकि एक वास्तविक प्रतिलिपि नहीं होती है।
लाइनपोगल

तब मुझे लगता है कि वहां SHARED data storageतक या जब तक आरक्षित नहीं है copy-on-write, लेकिन (मेरे कोड स्निपेट से) यह स्पष्ट है कि हमेशा SENTINEL variablesके लिए original arrayऔर अन्य के लिए एक के दो सेट होंगे foreach। धन्यवाद कि समझ में आता है
sakhunzai

1
हाँ जो "संभावित" प्रति है अर्थात "संभावित" प्रति है। जैसा कि आपने सुझाव दिया है कि संरक्षित नहीं है
सखूनजई

33

PHP 7 के लिए नोट

इस उत्तर पर अद्यतन करने के लिए क्योंकि इसने कुछ लोकप्रियता हासिल की है: यह उत्तर अब PHP के रूप में लागू नहीं होता है। 7. जैसा कि " पिछड़े असंगत परिवर्तन " में समझाया गया है , PHP में 7 फॉरेक्स कॉपी की सरणी पर काम करता है, इसलिए सरणी पर कोई भी परिवर्तन स्वयं फॉरेस्ट लूप पर परिलक्षित नहीं होते हैं। लिंक पर अधिक जानकारी

स्पष्टीकरण ( php.net से उद्धरण ):

पहला रूप array_expression द्वारा दिए गए सरणी पर लूप करता है। प्रत्येक पुनरावृत्ति पर, मौजूदा तत्व का मूल्य $ मूल्य को सौंपा गया है और आंतरिक सरणी सूचक एक से उन्नत है (इसलिए अगले पुनरावृत्ति पर, आप अगले तत्व को देख रहे होंगे)।

इसलिए, आपके पहले उदाहरण में आपके पास केवल एक तत्व है सरणी में, और जब पॉइंटर को स्थानांतरित किया जाता है तो अगला तत्व मौजूद नहीं होता है, इसलिए आपके द्वारा नए तत्व फ़ॉर्चर समाप्त करने के बाद जोड़ दिया जाता है क्योंकि यह पहले से ही "निर्णय लिया" है कि इसे अंतिम तत्व के रूप में देखें।

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

मेरा मानना ​​है कि दस्तावेज़ीकरण में स्पष्टीकरण के प्रत्येक पुनरावृत्ति भाग पर यह सब परिणाम है , जिसका अर्थ है कि foreachकोड को कॉल करने से पहले इसका सभी तर्क है {}

परीक्षण का मामला

यदि आप इसे चलाते हैं:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        $array['baz']=3;
        echo $v." ";
    }
    print_r($array);
?>

आपको यह आउटपुट मिलेगा:

1 2 3 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

जिसका अर्थ है कि यह संशोधन को स्वीकार करता है और इसके माध्यम से चला गया क्योंकि इसे "समय में" संशोधित किया गया था। लेकिन अगर आप ऐसा करते हैं:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        if ($k=='bar') {
            $array['baz']=3;
        }
        echo $v." ";
    }
    print_r($array);
?>

तुम्हे मिल जाएगा:

1 2 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

इसका मतलब यह है कि सरणी को संशोधित किया गया था, लेकिन जब से हमने इसे संशोधित किया था जब foreachपहले से ही सरणी के अंतिम तत्व पर था, तो उसने "निर्णय लिया" अब और लूप नहीं किया, और भले ही हमने नया तत्व जोड़ा, हमने इसे "बहुत देर से" जोड़ा और यह के माध्यम से पाला नहीं गया था।

विस्तृत विवरण में पढ़ा जा सकता है कि PHP 'foreach' वास्तव में कैसे काम करता है? जो इस व्यवहार के पीछे के इंटर्नल की व्याख्या करता है।


7
अच्छा तो आपने बाकी का जवाब पढ़ा? यह एकदम सही समझ में आता है कि अगर कोई दूसरी बार भी इससे पहले कि यह कोड चलाता है, तब तक फोरच्यू फैसला करता है।
डेकासिपोविक

2
नहीं, सरणी को संशोधित किया गया है, लेकिन "बहुत देर हो चुकी है" क्योंकि पहले से ही "सोचता है" कि यह अंतिम तत्व पर है (जो कि पुनरावृत्ति की शुरुआत में है) और अब लूप नहीं होगा। जहां दूसरे उदाहरण में, यह पुनरावृत्ति की शुरुआत में अंतिम तत्व पर नहीं है और अगले पुनरावृत्ति की शुरुआत पर फिर से मूल्यांकन करता है। मैं एक टेस्ट केस तैयार करने की कोशिश कर रहा हूं।
dasasipovic

1
पर @AlmaDo देखो lxr.php.net/xref/PHP_TRUNK/Zend/zend_vm_def.h#4509 यह हमेशा अगले सूचक करने के लिए सेट जब यह iterates है। इसलिए, जब यह अंतिम पुनरावृत्ति पर पहुंचता है, तो इसे समाप्त (NULL सूचक के माध्यम से) के रूप में चिह्नित किया जाएगा। जब आप फिर अंतिम पुनरावृत्ति में एक कुंजी जोड़ते हैं, तो फ़ॉरचैट इसकी सूचना नहीं देगा।
बोवेबी

1
@Dasasipovic no। वहाँ कोई पूर्ण और स्पष्ट व्याख्या नहीं है (कम से कम अभी के लिए - मैं गलत हो सकता हूं)
अल्मा दो

4
वास्तव में ऐसा लगता है कि @AlmaDo को अपने तर्क समझने में दोष है ... आपका उत्तर ठीक है।
बोवबी

15

PHP मैनुअल द्वारा प्रदान किए गए प्रलेखन के अनुसार।

प्रत्येक पुनरावृत्ति पर, वर्तमान तत्व का मान $ v को दिया जाता है और आंतरिक
सरणी सूचक एक से उन्नत होता है (इसलिए अगले पुनरावृत्ति पर, आप अगले तत्व को देखेंगे)।

तो आपके पहले उदाहरण के अनुसार:

$array = ['foo'=>1];
foreach($array as $k=>&$v)
{
   $array['bar']=2;
   echo($v);
}

$arrayकेवल एक ही तत्व है, इसलिए फ़ॉरच निष्पादन के अनुसार, 1 को असाइन करना है $vऔर इसके पास पॉइंटर को स्थानांतरित करने के लिए कोई अन्य तत्व नहीं है

लेकिन अपने दूसरे उदाहरण में:

$array = ['foo'=>1, 'bar'=>2];
foreach($array as $k=>&$v)
{
   $array['baz']=3;
   echo($v);
}

$arrayदो तत्व हैं, इसलिए अब $ सरणी शून्य सूचकांकों का मूल्यांकन करती है और सूचक को एक से आगे बढ़ाती है। लूप के पहले पुनरावृत्ति के लिए, $array['baz']=3;संदर्भ द्वारा पास के रूप में जोड़ा गया ।


13

महान सवाल, क्योंकि कई डेवलपर्स, यहां तक ​​कि अनुभवी भी, जिस तरह से PHP लूप फॉर्क्स लूप्स में हैंडल करते हैं, उससे भ्रमित होते हैं। मानक फ़ॉरेस्ट लूप में, PHP लूप में उपयोग किए जाने वाले सरणी की एक प्रतिलिपि बनाता है। लूप खत्म होने के तुरंत बाद कॉपी को छोड़ दिया जाता है। यह एक साधारण फॉर्च लूप के संचालन में पारदर्शी है। उदाहरण के लिए:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    echo "{$item}\n";
}

यह आउटपुट:

apple
banana
coconut

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

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $item = strrev ($item);
}

print_r($set);

यह आउटपुट:

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
)

मूल से कोई भी परिवर्तन नोटिस नहीं किया जा सकता है, वास्तव में मूल से कोई परिवर्तन नहीं हैं, भले ही आपने स्पष्ट रूप से $ आइटम को एक मूल्य सौंपा हो। ऐसा इसलिए है क्योंकि आप $ आइटम पर काम कर रहे हैं क्योंकि यह उस पर काम किए जा रहे $ सेट की कॉपी में दिखाई देता है। आप संदर्भ द्वारा $ आइटम को हथियाने के द्वारा इसे ओवरराइड कर सकते हैं, जैसे:

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $item = strrev($item);
}
print_r($set);

यह आउटपुट:

Array
(
    [0] => elppa
    [1] => ananab
    [2] => tunococ
)

तो यह स्पष्ट और अवलोकनीय है, जब $ आइटम को उप-संदर्भ पर संचालित किया जाता है, तो $ आइटम में किए गए परिवर्तन मूल $ सेट के सदस्यों के लिए किए जाते हैं। संदर्भ द्वारा $ आइटम का उपयोग करना भी PHP को कॉपी कॉपी बनाने से रोकता है। इसका परीक्षण करने के लिए, पहले हम एक त्वरित स्क्रिप्ट दिखाएंगे जो प्रतिलिपि प्रदर्शित करेगी:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $set[] = ucfirst($item);
}
print_r($set);

यह आउटपुट:

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
    [3] => Apple
    [4] => Banana
    [5] => Coconut
)

जैसा कि उदाहरण में दिखाया गया है, PHP ने $ सेट की नकल की और इसे लूप ओवर के लिए इस्तेमाल किया, लेकिन जब $ सेट को लूप के अंदर इस्तेमाल किया गया, तो PHP ने वेरिएबल्स को मूल सरणी में जोड़ा, न कि कॉपी किए गए एरे को। मूल रूप से, PHP केवल लूप के निष्पादन और $ आइटम के असाइनमेंट के लिए कॉपी किए गए सरणी का उपयोग कर रहा है। इस वजह से, ऊपर का लूप केवल 3 बार निष्पादित होता है, और हर बार मूल $ सेट के अंत में एक और मूल्य जोड़ देता है, मूल $ 6 तत्व के साथ सेट छोड़ देता है, लेकिन कभी भी अनंत लूप में प्रवेश नहीं करता है।

हालांकि, क्या होगा अगर हमने संदर्भ द्वारा $ आइटम का उपयोग किया था, जैसा कि मैंने पहले उल्लेख किया है? उपरोक्त परीक्षण में एक एकल वर्ण जोड़ा गया:

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $set[] = ucfirst($item);
}
print_r($set);

एक अनंत लूप में परिणाम। ध्यान दें कि यह वास्तव में एक अनन्त लूप है, आपको या तो स्क्रिप्ट को मारना होगा या मेमोरी से बाहर निकलने के लिए अपने ओएस की प्रतीक्षा करनी होगी। मैंने अपनी स्क्रिप्ट में निम्न पंक्ति जोड़ी, इसलिए PHP बहुत तेज़ी से मेमोरी से बाहर हो जाएगी, मेरा सुझाव है कि यदि आप इन अनंत लूप परीक्षणों को चलाने जा रहे हैं तो आप भी ऐसा ही करें:

ini_set("memory_limit","1M");

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


7

PHP foreach लूप का उपयोग किया जा सकता है Indexed arrays, Associative arraysऔर Object public variables

फ़ॉरच लूप में, पहली बात यह है कि यह उस सरणी की एक प्रति बनाता है जिस पर चलना है। PHP तब copyमूल के बजाय सरणी के इस नए पर पुनरावृति करता है। यह निम्न उदाहरण में दिखाया गया है:

<?php
$numbers = [1,2,3,4,5,6,7,8,9]; # initial values for our array
echo '<pre>', print_r($numbers, true), '</pre>', '<hr />';
foreach($numbers as $index => $number){
    $numbers[$index] = $number + 1; # this is making changes to the origial array
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # showing data from the copied array
}
echo '<hr />', '<pre>', print_r($numbers, true), '</pre>'; # shows the original values (also includes the newly added values).

इसके अलावा, php भी उपयोग करने की अनुमति देता है iterated values as a reference to the original array value। यह नीचे प्रदर्शित किया गया है:

<?php
$numbers = [1,2,3,4,5,6,7,8,9];
echo '<pre>', print_r($numbers, true), '</pre>';
foreach($numbers as $index => &$number){
    ++$number; # we are incrementing the original value
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # this is showing the original value
}
echo '<hr />';
echo '<pre>', print_r($numbers, true), '</pre>'; # we are again showing the original value

नोट: इसका original array indexesउपयोग करने की अनुमति नहीं है references

स्रोत: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-explples


1
Object public variablesगलत या सबसे भ्रामक है। आप सही इंटरफ़ेस के बिना किसी सरणी में किसी ऑब्जेक्ट का उपयोग नहीं कर सकते (उदाहरण के लिए, ट्रैवर्सिबल) और जब आप करते foreach((array)$obj ...हैं तो आप वास्तव में एक साधारण सरणी के साथ काम कर रहे हैं, अब कोई ऑब्जेक्ट नहीं।
ईसाई धर्म
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.