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


मुझे यह कहकर उपसर्ग करना है कि मुझे पता है कि क्या 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 पहले निष्पादन शुरू होता है, आंतरिक सरणी सूचक स्वचालित रूप से सरणी के पहले तत्व पर रीसेट हो जाता है।

ठीक है ... ऐसा लगता है कि यह सुझाव देता है 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 मैनुअल यह भी कहता है:

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

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

टेस्ट केस 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 लूप के नतीजे को प्रभावित कर सकता है?

1644
2018-04-07 19:33


मूल


@ डेव रैंडम एक है php-internals यह टैग संभवतः साथ जाना चाहिए, लेकिन मैं यह तय करने के लिए आपको छोड़ दूंगा कि अगर 5 अन्य टैग्स को प्रतिस्थापित किया जाए तो कौन सा है। - Michael Berkowski
गायब संभाल के बिना गाय की तरह दिखता है - zb'
पहले मैंने सोचा »भगवान, एक और नौसिखिया सवाल। दस्तावेज़ पढ़ें ... एचएम, स्पष्ट रूप से अपरिभाषित व्यवहार «। तब मैंने पूरा सवाल पढ़ा, और मुझे कहना होगा: मुझे यह पसंद है। आपने इसमें कुछ प्रयास किए हैं और सभी टेस्टकेस लिख रहे हैं। ps। टेस्टकेस 4 और 5 वही हैं? - knittl
इस बारे में एक विचार है कि यह क्यों समझ में आता है कि सरणी सूचक स्पर्श हो जाता है: PHP को रीसेट करने और प्रतिलिपि के साथ मूल सरणी के आंतरिक सरणी सूचक को स्थानांतरित करने की आवश्यकता होती है, क्योंकि उपयोगकर्ता वर्तमान मान के संदर्भ के लिए पूछ सकता है (foreach ($array as &$value)) - PHP को मूल सरणी में वर्तमान स्थिति को जानने की आवश्यकता है, भले ही यह वास्तव में एक प्रतिलिपि पर फिर से चल रहा हो। - Niko
@ सेन: आईएमएचओ, पीएचपी दस्तावेज कोर भाषा सुविधाओं की बारीकियों का वर्णन करने में वास्तव में काफी खराब है। लेकिन यह शायद, क्योंकि, इतने सारे विज्ञापन-विशेष मामलों को भाषा में बेक किया जाता है ... - Oliver Charlesworth


जवाब:


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 "arrays" में वास्तव में शब्दकोशों का आदेश दिया जाता है और उन्हें इस आदेश के अनुसार पार किया जाएगा (जो सम्मिलन आदेश से मेल खाता है जब तक आप कुछ ऐसा नहीं करते sort)। यह चाबियों के प्राकृतिक क्रम (अन्य भाषाओं में सूचियां अक्सर काम करती हैं) या फिर कोई परिभाषित आदेश नहीं होने का विरोध किया जाता है (अन्य भाषाओं में शब्दकोश अक्सर काम करते हैं)।

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

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

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

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

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

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

PHP 5

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

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

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

// 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 निम्नलिखित schenanigans निष्पादित करता है: लूप निकाय को निष्पादित करने से पहले, foreach वर्तमान तत्व के लिए एक सूचक का बैक अप लेंगे और इसके हैश प्रति-foreach में HashPointer। लूप बॉडी रन के बाद, आईएपी इस तत्व पर वापस सेट हो जाएगा यदि यह अभी भी मौजूद है। यदि तत्व हटा दिया गया है, तो हम वर्तमान में आईएपी पर मौजूद व्हीवर का उपयोग करेंगे। यह योजना ज्यादातर प्रकार के प्रकार के काम करता है, लेकिन इसमें बहुत अजीब व्यवहार है जिससे आप इससे बाहर निकल सकते हैं, जिनमें से कुछ मैं नीचे दिखाऊंगा।

ऐरे डुप्लिकेशन

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

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

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

इस कोड को उदाहरण के रूप में देखें जहां डुप्लिकेशन होता है:

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

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

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

(*) यहां रेफकाउंट बढ़ाने से निर्दोष लगता है, लेकिन कॉपी-ऑन-राइट (गाय) अर्थशास्त्र का उल्लंघन करता है: इसका मतलब है कि हम एक refcount = 2 सरणी के आईएपी को संशोधित करने जा रहे हैं, जबकि गाय निर्देशित करता है कि संशोधनों को केवल refcount पर ही किया जा सकता है = 1 मान इस उल्लंघन के परिणामस्वरूप उपयोगकर्ता-दृश्य व्यवहार में परिवर्तन होता है (जबकि गाय सामान्य रूप से पारदर्शी होता है), क्योंकि पुनरावृत्त सरणी पर आईएपी परिवर्तन देखा जा सकता है - लेकिन केवल सरणी पर पहले गैर-आईएपी संशोधन तक ही। इसके बजाए, तीन "मान्य" विकल्प हमेशा एक डुप्लिकेट करने के लिए होते थे, बी) रिफॉउंट बढ़ाने में वृद्धि नहीं करते हैं और इस प्रकार पुनरावर्तित सरणी को लूप में मनमाने ढंग से संशोधित करने की इजाजत दी जाती है, या सी) आईएपी का बिल्कुल उपयोग नहीं करते हैं ( 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, आईएपी पहले से ही तत्व पर है $i+1। यही कारण है कि पुनरावृत्ति के दौरान संशोधन दिखाने वाले कोड नमूने हमेशा अनसेट करेंगे आगामी तत्व, वर्तमान की बजाय।

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

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

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

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

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

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

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

विभिन्न डुप्लिकेशन व्यवहार दिखाने का एक अच्छा तरीका है के व्यवहार का पालन करना 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 */

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

एक और छोटी विविधता, इस बार हम सरणी को किसी अन्य चर में आवंटित करेंगे:

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

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

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

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

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

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 हटा दिया गया था। शायद अप्रत्याशित रूप से यह है कि बाहरी लूप पहले तत्व के बाद बंद हो जाता है। ऐसा क्यों है?

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

का एक और परिणाम 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() अस्थायी रूप से आईएपी को संशोधित करता है, इसे लूप बॉडी के बाद वर्तमान foreach तत्व में बहाल किया जाएगा। जबरदस्ती करना 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', और आवंटक तत्व को संग्रहीत करने के लिए उसी स्मृति स्थान का पुन: उपयोग करना होता है। इसलिए foreach सीधे नए डालने वाले तत्व पर कूदता है, इस प्रकार लूप को छोटा कर देता है।

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

एक आखिरी अजीब मामला जिसे मैं उल्लेख करना चाहता हूं, यह है कि 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 ने इस उद्देश्य के लिए एक आंतरिक सरणी सूचक (आईएपी) का उपयोग किया, जो कुछ हद तक उप-स्थानिक था, क्योंकि एक सरणी सूचक को एकाधिक एक साथ फोरच लूप का समर्थन करने के लिए बढ़ाया जाना था तथा साथ बातचीत reset() आदि के शीर्ष पर।

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

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

ऐरे डुप्लिकेशन

PHP 5 और PHP 7 के बीच एक और महत्वपूर्ण परिवर्तन सरणी डुप्लिकेशन से संबंधित है। अब जब आईएपी का उपयोग नहीं किया जाता है, तो मूल्य-सारणी पुनरावृत्ति केवल सभी मामलों में एक रेफउंट वृद्धि (सरणी को डुप्लिकेशंस के बजाय) ही करेगी। यदि सरणी फ़ोरैच लूप के दौरान संशोधित की जाती है, तो उस बिंदु पर एक डुप्लिकेशन होगा (प्रति-लिखने के अनुसार) और 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 एक ही आउटपुट को बनाए रखते हैं: बाय-वैल्यू सरणी पुनरावृत्ति हमेशा मूल तत्वों पर काम करती रहती है। (इस मामले में PHP 5 और PHP 7 के बीच भी refcounting और डुप्लिकेशन व्यवहार समान है)।

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

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

उदाहरणों का दूसरा सेट व्यवहार से संबंधित था current() विभिन्न संदर्भ / 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

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


1384
2018-02-13 13:21



@ बाबा यह करता है। इसे किसी फ़ंक्शन में पास करना वही है जैसा करना है $foo = $array लूप से पहले;) - NikiC
आप में से उन लोगों के लिए जो ज्वलंत नहीं जानते हैं, कृपया सारा गोलेमैन का संदर्भ लें blog.golemon.com/2007/01/youre-being-lied-to.html - shu zOMG chen
@unbeli मैं PHP द्वारा आंतरिक रूप से उपयोग की जाने वाली शब्दावली का उपयोग कर रहा हूं। Bucketहैश टकराव के लिए दोगुनी लिंक्ड सूची का हिस्सा हैं और ऑर्डर के लिए दोगुनी लिंक्ड सूची का भी हिस्सा हैं;) - NikiC
@NikiC: क्या आपको वास्तव में विश्वविद्यालय में जाना है? आप केवल 1 9 हैं लेकिन आप अधिक अनुभवी दिखते हैं - dynamic
महान anwser। मुझे लगता है तुम्हारा मतलब है iterate($outerArr); और नहीं iterate($arr); कहीं। - niahoo


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

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 टर्नरी ऑपरेटर: तेज़ या नहीं?


97
2018-04-07 20:43



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


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

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

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

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

ढेर ओवरफ्लो सवाल यह सुनिश्चित करने के लिए कि PHP में 'foreach' लूप में मान रीसेट किया गया है? आपके प्रश्न के मामलों (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

34
2018-04-07 21:03



आपका जवाब बिल्कुल सही नहीं है। foreach सरणी की एक संभावित प्रति पर काम करता है, लेकिन जब तक इसकी आवश्यकता नहीं होती है तब तक यह वास्तविक प्रतिलिपि नहीं बनाती है। - linepogl
क्या आप यह प्रदर्शित करना चाहते हैं कि कोड के माध्यम से उस संभावित प्रति को कैसे और कब बनाया जाए? मेरा कोड यह दर्शाता है कि foreach सरणी 100% समय की प्रतिलिपि बना रहा है। मैं जानना चाहता हूं। आपकी टिप्पणियों के लिए धन्यवाद - sakhunzai
एक सरणी की प्रतिलिपि बहुत अधिक है। 100000 तत्वों के साथ किसी भी सरणी को फिर से चलाने के लिए लगने वाले समय को गिनने का प्रयास करें for या foreach। आप दोनों के बीच कोई महत्वपूर्ण अंतर नहीं देखेंगे, क्योंकि एक वास्तविक प्रति नहीं होती है। - linepogl
तो मुझे लगता है कि वहाँ है SHARED data storage आरक्षित या जब तक आरक्षित copy-on-write , लेकिन (मेरे कोड स्निपेट से) यह स्पष्ट है कि हमेशा दो सेट होंगे SENTINEL variables एक के लिए एक original array और अन्य के लिए foreach। धन्यवाद कि समझ में आता है - sakhunzai
"Prospected"? क्या आपका मतलब "संरक्षित" है? - Peter Mortensen


PHP 7 के लिए नोट करें

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

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

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

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

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

मुझे विश्वास है कि यह सब परिणाम है प्रत्येक पुनरावृत्ति पर दस्तावेज में स्पष्टीकरण का हिस्सा, जिसका शायद मतलब है 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' वास्तव में कैसे काम करता है? जो इस व्यवहार के पीछे आंतरिक बताता है।


22
2018-04-15 08:46



क्या आपने बाकी जवाब पढ़ा? यह सही समझ में आता है कि foreach तय करता है कि यह एक और बार लूप होगा से पहले यह इसमें कोड भी चलाता है। - Damir Kasipovic
नहीं, सरणी संशोधित है, लेकिन "बहुत देर हो चुकी है" क्योंकि पहले से ही "सोचता है" कि यह अंतिम तत्व (जो कि पुनरावृत्ति की शुरुआत में है) पर है और अब लूप नहीं होगा। दूसरे उदाहरण में, यह पुनरावृत्ति की शुरुआत में अंतिम तत्व पर नहीं है और अगले पुनरावृत्ति की शुरुआत में फिर से मूल्यांकन करता है। मैं एक टेस्ट केस तैयार करने की कोशिश कर रहा हूं। - Damir Kasipovic
@AlmaDo देखो lxr.php.net/xref/PHP_TRUNK/Zend/zend_vm_def.h#4509 जब यह पुनरावृत्त होता है तो यह हमेशा अगले सूचक पर सेट होता है। इसलिए, जब यह अंतिम पुनरावृत्ति तक पहुंच जाता है, तो इसे समाप्त होने के रूप में चिह्नित किया जाएगा (नल पॉइंटर के माध्यम से)। जब आप अंतिम पुनरावृत्ति में एक कुंजी जोड़ते हैं, तो foreach इसे नोटिस नहीं करेगा। - bwoebi
@DKasipovic संख्या। कोई नहीं है पूर्ण और स्पष्ट वहां स्पष्टीकरण (कम से कम अभी के लिए - हो सकता है कि मैं गलत हूं) - Alma Do
असल में ऐसा लगता है कि @ अल्माडो को अपने तर्क को समझने में कोई दोष है ... आपका जवाब ठीक है। - bwoebi


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

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

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

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

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

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

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

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


8
2018-04-15 09:32





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

$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 ने $ सेट की प्रतिलिपि बनाई है और इसे लूप पर इस्तेमाल किया है, लेकिन जब लूप के अंदर $ set का उपयोग किया गया था, तो 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 को सरणी की एक प्रतिलिपि बनाने के लिए क्यों लिखा गया था। जब एक प्रतिलिपि बनाई जाती है और केवल लूप निर्माण की संरचना द्वारा ही उपयोग की जाती है, तो सरणी लूप के निष्पादन के दौरान स्थैतिक रहता है, इसलिए आप कभी भी मुद्दों में भाग नहीं लेंगे।


5
2018-04-21 08:44





PHP foreach पाश के साथ इस्तेमाल किया जा सकता है Indexed arrays, Associative arrays तथा Object public variables

फोरैच लूप में, पहली चीज php करता है कि यह उस सरणी की एक प्रति बनाता है जिसे पुन: चालू किया जाना है। 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-examples


4
2017-11-13 14:08



Object public variables गलत है या सबसे अच्छा भ्रामक है। आप सही इंटरफ़ेस (उदाहरण के लिए, ट्रैवर्सिबल) के बिना किसी ऑब्जेक्ट में किसी ऑब्जेक्ट का उपयोग नहीं कर सकते हैं और जब आप करते हैं foreach((array)$obj ... आप वास्तव में एक साधारण सरणी के साथ काम कर रहे हैं, अब एक ऑब्जेक्ट नहीं। - Christian