आप इन चीजों को बड़े पैमाने पर कर सकते हैं, क्योंकि वे वास्तव में ऐसा करने में मुश्किल नहीं हैं।
संकलक के दृष्टिकोण से, किसी अन्य फ़ंक्शन के अंदर एक फ़ंक्शन घोषणा को लागू करने के लिए बहुत तुच्छ है। कंपाइलर को किसी भी फ़ंक्शन के अंदर अन्य घोषणाओं (जैसे, int x;
) को संभालने के लिए फ़ंक्शन के अंदर घोषणाओं को अनुमति देने के लिए एक तंत्र की आवश्यकता होती है।
यह आमतौर पर एक घोषणा को पार्स करने के लिए एक सामान्य तंत्र होगा। कंपाइलर लिखने वाले व्यक्ति के लिए, यह वास्तव में बिल्कुल भी मायने नहीं रखता है कि क्या किसी अन्य फ़ंक्शन के कोड को अंदर या बाहर पार्स करते समय यह तंत्र लागू होता है - यह सिर्फ एक घोषणा है, इसलिए जब यह जानने के लिए पर्याप्त होता है कि एक घोषणा क्या है, यह संकलक के भाग को आमंत्रित करता है जो घोषणाओं से संबंधित है।
वास्तव में, किसी फ़ंक्शन के अंदर इन विशेष घोषणाओं को रोकना संभवतः अतिरिक्त जटिलता को जोड़ देगा, क्योंकि कंपाइलर को यह देखने के लिए पूरी तरह से आभारी जांच की आवश्यकता होगी कि क्या यह पहले से ही एक फ़ंक्शन परिभाषा के अंदर कोड को देख रहा है और इसके आधार पर तय करता है कि क्या इस विशेष को अनुमति देने या निषेध करने के लिए घोषणा।
यह सवाल छोड़ देता है कि एक नेस्टेड फ़ंक्शन अलग कैसे है। एक नेस्टेड फ़ंक्शन अलग है क्योंकि यह कोड पीढ़ी को कैसे प्रभावित करता है। नेस्टेड फ़ंक्शंस की अनुमति देने वाली भाषाओं में (उदाहरण के लिए, पास्कल) आप आमतौर पर यह उम्मीद करते हैं कि नेस्टेड फ़ंक्शन में कोड फ़ंक्शन के चर तक सीधी पहुंच है जिसमें यह नेस्टेड है। उदाहरण के लिए:
int foo() {
int x;
int bar() {
x = 1;
}
}
स्थानीय कार्यों के बिना, स्थानीय चर का उपयोग करने का कोड काफी सरल है। एक विशिष्ट कार्यान्वयन में, जब निष्पादन फ़ंक्शन में प्रवेश करता है, तो स्थानीय चर के लिए अंतरिक्ष का कुछ ब्लॉक स्टैक पर आवंटित किया जाता है। सभी स्थानीय चर उस एकल खंड में आवंटित किए जाते हैं, और प्रत्येक चर को ब्लॉक की शुरुआत (या अंत) से केवल एक ऑफसेट के रूप में माना जाता है। उदाहरण के लिए, आइए एक फ़ंक्शन पर कुछ इस तरह विचार करें:
int f() {
int x;
int y;
x = 1;
y = x;
return y;
}
एक कंपाइलर (यह मानते हुए कि यह अतिरिक्त कोड का अनुकूलन नहीं करता है) इस के बराबर कोड उत्पन्न कर सकता है:
stack_pointer -= 2 * sizeof(int);
x_offset = 0;
y_offset = sizeof(int);
stack_pointer[x_offset] = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];
return_location = stack_pointer[y_offset];
stack_pointer += 2 * sizeof(int);
विशेष रूप से, इसमें स्थानीय चर के ब्लॉक की शुरुआत की ओर इशारा करते हुए एक स्थान है, और स्थानीय चर तक सभी पहुंच उस स्थान से ऑफसेट के रूप में है।
नेस्टेड फ़ंक्शंस के साथ, अब ऐसा नहीं है - इसके बजाय, एक फ़ंक्शन की न केवल अपने स्थानीय चर तक पहुंच होती है, बल्कि उन सभी फ़ंक्शंस के लिए वैरिएबल स्थानीय तक पहुंचता है जिसमें यह नेस्टेड है। इसके बजाय केवल एक "stack_pointer" होने से जिससे यह एक ऑफसेट की गणना करता है, इसे stack_pointers को उन कार्यों के लिए स्थानीय खोजने के लिए स्टैक को फिर से चलना पड़ता है जिसमें यह नेस्टेड है।
अब, एक ऐसे तुच्छ मामले में, जो इतना भी भयानक नहीं है - अगर bar
अंदर घोंसला है foo
, तो bar
बस पिछले ढेर सूचक पर ढेर को देखने के लिए foo
चर तक पहुंच सकते हैं । सही?
गलत! खैर, ऐसे मामले हैं जहां यह सच हो सकता है, लेकिन जरूरी नहीं कि यह मामला ही हो। विशेष रूप से, bar
पुनरावर्ती हो सकता है, जिस स्थिति में दिया गया आह्वानbar
आसपास के फ़ंक्शन के चर को खोजने के लिए स्टैक का स्तर लगभग कुछ मनमानी संख्या को देखना पड़ सकता है। आम तौर पर, आपको दो चीजों में से एक करने की आवश्यकता होती है: या तो आप स्टैक पर कुछ अतिरिक्त डेटा डालते हैं, इसलिए यह रन-टाइम पर स्टैक को उसके आसपास के फ़ंक्शन के स्टैक फ्रेम को खोजने के लिए खोज सकता है, या फिर आप प्रभावी रूप से एक पॉइंटर पास करने के लिए नेस्टेड फ़ंक्शन के लिए एक छिपे हुए पैरामीटर के रूप में आसपास के फ़ंक्शन के स्टैक फ़्रेम। ओह, लेकिन जरूरी नहीं कि केवल एक ही आसपास का कार्य हो - यदि आप फ़ंक्शन को घोंसला कर सकते हैं, तो आप संभवतः उन्हें (अधिक या कम) मनमाने ढंग से गहरा घोंसला दे सकते हैं, इसलिए आपको छिपे हुए मापदंडों की एक मनमानी संख्या पारित करने के लिए तैयार होने की आवश्यकता है। इसका मतलब है कि आप आम तौर पर आसपास के कार्यों के लिए स्टैक फ्रेम की एक लिंक्ड सूची की तरह कुछ के साथ समाप्त होते हैं,
हालांकि, इसका मतलब है कि "स्थानीय" चर तक पहुंच एक मामूली मामला नहीं हो सकता है। चर का उपयोग करने के लिए सही स्टैक फ्रेम खोजना गैर-तुच्छ हो सकता है, इसलिए आसपास के कार्यों के चर तक पहुंच वास्तव में स्थानीय चर तक पहुंच की तुलना में धीमी (कम से कम आमतौर पर) धीमी होती है। और, निश्चित रूप से, कंपाइलर को सही स्टैक फ्रेम खोजने के लिए कोड उत्पन्न करना पड़ता है, स्टैक फ्रेम के किसी भी मनमाने संख्या के माध्यम से चर का उपयोग करना, और इसी तरह।
यह जटिलता है कि सी नेस्टेड कार्यों को प्रतिबंधित करने से बच रही थी। अब, यह निश्चित रूप से सच है कि एक वर्तमान सी ++ कंपाइलर 1970 के विंटेज सी कंपाइलर से एक अलग तरह का जानवर है। मल्टीपल, वर्चुअल इनहेरिटेंस जैसी चीजों के साथ, C ++ कंपाइलर को किसी भी स्थिति में इसी सामान्य प्रकृति की चीजों से निपटना पड़ता है (यानी, ऐसे मामलों में बेस-क्लास वेरिएबल का स्थान ढूंढना गैर-तुच्छ भी हो सकता है)। प्रतिशत के आधार पर, नेस्टेड फ़ंक्शंस का समर्थन करना वर्तमान C ++ कंपाइलर (और कुछ, जैसे कि जीसीसी, पहले से ही उनका समर्थन करता है) में बहुत जटिलता नहीं जोड़ेगा।
इसी समय, यह शायद ही कभी बहुत उपयोगिता जोड़ता है। विशेष रूप से, अगर आप कुछ है कि निर्धारित करना चाहते हैं में कार्य करता है एक समारोह के एक समारोह के अंदर की तरह, आप एक लैम्ब्डा अभिव्यक्ति का उपयोग कर सकते हैं। यह वास्तव में जो बनाता है वह एक ऑब्जेक्ट (यानी, कुछ वर्ग का उदाहरण) है जो फ़ंक्शन कॉल ऑपरेटर ( operator()
) को ओवरलोड करता है, लेकिन यह अभी भी फ़ंक्शन जैसी क्षमताएं देता है। यह आसपास के संदर्भ से डेटा को कैप्चर करना (या नहीं) अधिक स्पष्ट करता है, जो इसे इसके उपयोग के लिए एक पूरे नए तंत्र और नियमों के सेट का आविष्कार करने के बजाय मौजूदा तंत्र का उपयोग करने की अनुमति देता है।
निचला रेखा: भले ही यह शुरू में ऐसा लगे कि नेस्टेड घोषणाएँ कठिन हैं और नेस्टेड कार्य तुच्छ हैं, कमोबेश यह विपरीत सच है: नेस्टेड फ़ंक्शन वास्तव में नेस्टेड घोषणाओं की तुलना में समर्थन के लिए बहुत अधिक जटिल हैं।
one
एक फ़ंक्शन परिभाषा है , अन्य दो घोषणाएं हैं ।