सी, पायथन, जावास्क्रिप्ट, गो, रस्ट, सॉलिडिटी जैसी भाषाओं और कंप्यूटर साइंस के बुनियादी और गणित के उन्नत सिद्धांतों को हिंदी में पढ़ने के लिए संपर्क करें। हम वेबसाइट और सॉफ्टवेयर भी बनाते हैं।
<!-- Beej's guide to C
# vim: ts=4:sw=4:nosi:et:tw=72
-->
फ़ंक्शन {#functions}
"सर, ऐसे माहौल में नहीं। इसीलिए मुझे तीस से ज़्यादा गौण फ़ंक्शनों के
लिए भी प्रोग्राम किया गया है, जो---"
---सी-थ्रीपीओ[i[C3PO]], बेरहमी से बीच में टोके जाने से पहले, अतिरिक्त
फ़ंक्शनों की एक संख्या बताते हुए जो अब प्रभावित नहीं करती, स्टार वॉर्स
की पटकथा
[i[Functions]<]
दूसरी भाषाओं की तरह, जिनसे आप परिचित हैं, सी में भी फ़ंक्शन की
अवधारणा है।
फ़ंक्शन तरह-तरह के आर्ग्युमेंट[i[Function arguments]] ले सकते हैं और एक
मान लौटा सकते हैं। एक बात ज़रूरी है: आर्ग्युमेंट और रिटर्न मान के टाइप
पहले से घोषित होते हैं---क्योंकि सी को यही पसंद है!
आइए एक फ़ंक्शन देखें। यह फ़ंक्शन एक int आर्ग्युमेंट लेता है और एक
int लौटाता[i[return statement]] है।
#include <stdio.h>
int plus_one(int n) // The "definition"
{
return n + 1;
}
plus_one से पहले का int रिटर्न टाइप बताता है।
int n बताता है कि यह फ़ंक्शन एक int आर्ग्युमेंट लेता है, जो पैरामीटरn[i[Function parameters]] में रखा जाता है। पैरामीटर एक विशेष प्रकार का
लोकल वेरिएबल है, जिसमें आर्ग्युमेंट कॉपी किए जाते हैं।
यहाँ मैं इस बात पर ज़ोर देना चाहता हूँ कि आर्ग्युमेंट पैरामीटर में कॉपी
होते हैं। सी की बहुत-सी बातें तब आसान हो जाती हैं जब आप जान लें कि
पैरामीटर आर्ग्युमेंट की कॉपी है, आर्ग्युमेंट स्वयं नहीं। इस पर थोड़ी देर
में और बात करेंगे।
main() में आगे बढ़ते हुए हमें फ़ंक्शन की कॉल दिखती है, जहाँ हम रिटर्न
मान को लोकल वेरिएबल j में डालते हैं:
int main(void)
{
int i = 10, j;
j = plus_one(i); // The "call"
printf("i + 1 is %d\n", j);
}
भूलने से पहले एक बात: ध्यान दीजिए कि मैंने फ़ंक्शन को इस्तेमाल करने से
पहले परिभाषित किया है। ऐसा न करता, तो main() को कंपाइल करते समय
कंपाइलर को उसका पता ही नहीं होता और वह अज्ञात फ़ंक्शन कॉल की एरर दे
देता। ऊपर का कोड फ़ंक्शन प्रोटोटाइप के साथ ज़्यादा सही तरीके से भी
लिखा जा सकता है, पर उस पर बाद में आएँगे।
यह भी देखिए कि main()[i[main() function]] भी एक फ़ंक्शन है!
यह एक int लौटाता है।
पर यह void[i[void type]] क्या चीज़ है? यह एक कीवर्ड है, जो बताता है
कि फ़ंक्शन कोई आर्ग्युमेंट नहीं लेता।
आप रिटर्न टाइप void भी रख सकते हैं, यानी फ़ंक्शन कोई मान नहीं लौटाता:
#include <stdio.h>
// This function takes no arguments and returns no value:
void hello(void)
{
printf("Hello, world!\n");
}
int main(void)
{
hello(); // Prints "Hello, world!"
}
मान द्वारा पास करना {#passvalue}
[i[Pass by value]<]मैंने पहले बताया था कि जब आप किसी फ़ंक्शन को आर्ग्युमेंट
पास करते हैं, तो उस आर्ग्युमेंट की एक कॉपी बनती है और संबंधित पैरामीटर में
रखी जाती है।
अगर आर्ग्युमेंट कोई वेरिएबल है, तो उस वेरिएबल के मान की एक कॉपी बनकर
पैरामीटर में रखी जाती है।
अधिक सामान्य रूप से, पूरे आर्ग्युमेंट एक्सप्रेशन का मूल्यांकन होता है और
उसका मान तय होता है। वही मान पैरामीटर में कॉपी होता है।
किसी भी हाल में, पैरामीटर का मान अपने आप में स्वतंत्र है। फ़ंक्शन कॉल
करते समय आपने आर्ग्युमेंट के रूप में जो भी मान या वेरिएबल दिए थे, उनसे
उसका कोई लेना-देना नहीं रहता।
तो यहाँ एक उदाहरण देखें। इसे ध्यान से पढ़िए और चलाने से पहले आउटपुट का
अंदाज़ा लगाइए:
#include <stdio.h>
void increment(int a)
{
a++;
}
int main(void)
{
int i = 10;
increment(i);
printf("i == %d\n", i); // What does this print?
}
पहली नज़र में लगता है कि i का मान 10 है और हम उसे increment()
फ़ंक्शन को देते हैं। वहाँ मान बढ़ता है, इसलिए प्रिंट करने पर 11 आना
चाहिए, है न?
"निराशा की आदत डाल लो।"
---ड्रेड पाइरेट रॉबर्ट्स, द प्रिंसेस ब्राइड
पर यह 11 नहीं है---यह 10 प्रिंट करता है! कैसे?
सारा खेल इस बात का है कि फ़ंक्शन को दिए गए एक्सप्रेशन अपने संबंधित
पैरामीटर में कॉपी हो जाते हैं। पैरामीटर कॉपी है, असली नहीं।
तो main() में i का मान 10 है। और हम उसे increment() को देते हैं।
उस फ़ंक्शन में संबंधित पैरामीटर का नाम a है।
और कॉपी ऐसे होती है मानो असाइनमेंट से हुई हो। मोटे तौर पर, a = i। उस
समय a का मान 10 है। और main() में i का मान भी 10 है।
फिर हम a को बढ़ाकर 11 कर देते हैं। पर हम i को छूते तक नहीं! वह 10
ही रहता है।
आख़िर में फ़ंक्शन पूरा हो जाता है। उसके सारे लोकल वेरिएबल त्याग दिए जाते
हैं (अलविदा, a!) और हम main() में लौट आते हैं, जहाँ i अब भी 10
है।
और हम उसे प्रिंट करते हैं, 10 मिलता है, और बात ख़त्म।
इसीलिए पिछले plus_one() वाले उदाहरण में हमने बदला हुआ लोकल मान
return किया था, ताकि उसे main() में दोबारा देख सकें।
थोड़ा सीमित लगता है न? जैसे फ़ंक्शन से आप सिर्फ़ एक ही डेटा वापस पा
सकते हैं, यही सोच रहे हैं ना आप। फिर भी, डेटा वापस पाने का एक और तरीका
है; सी वाले इसे रेफ़रेंस द्वारा पास करना कहते हैं, और वह कहानी हम किसी
और समय सुनाएँगे।
पर किसी भी भारी-भरकम नाम के झाँसे में न आइए: फ़ंक्शन को आप जो कुछ भी
पास करते हैं, बिना किसी अपवाद के, वह उसके संबंधित पैरामीटर में कॉपी
होता है, और फ़ंक्शन उसी लोकल कॉपी पर काम करता है, चाहे कुछ भी हो। यह
बात याद रखिए, तब भी जब हम इस तथाकथित रेफ़रेंस द्वारा पास करने की बात कर
रहे हों।
[i[Pass by value]>]
फ़ंक्शन प्रोटोटाइप {#prototypes}
[i[Function prototypes]<]तो अगर आपको कुछ खंड पहले, हिम-युग के ज़माने की
बात याद हो, तो मैंने कहा था कि फ़ंक्शन को इस्तेमाल करने से पहले परिभाषित
करना पड़ता है, वरना कंपाइलर को उसकी पहले से जानकारी नहीं होगी और वह एरर
देकर बैठ जाएगा।
यह बात पूरी तरह सही नहीं है। आप कंपाइलर को पहले से बता सकते हैं कि आप
किसी ख़ास टाइप का फ़ंक्शन इस्तेमाल करेंगे, जिसकी एक ख़ास पैरामीटर सूची है।
ऐसे में फ़ंक्शन कहीं भी परिभाषित हो सकता है (किसी दूसरी फ़ाइल में भी),
बशर्ते उसे कॉल करने से पहले फ़ंक्शन प्रोटोटाइप घोषित हो चुका हो।
सौभाग्य से फ़ंक्शन प्रोटोटाइप बहुत आसान है। वह फ़ंक्शन परिभाषा की पहली
पंक्ति की ही कॉपी है, जिसके अंत में पक्का करने के लिए एक सेमीकोलन जोड़
दिया जाता है। उदाहरण के लिए, यह कोड बाद में परिभाषित फ़ंक्शन को कॉल करता
है, क्योंकि प्रोटोटाइप पहले घोषित है:
#include <stdio.h>
int foo(void); // This is the prototype!
int main(void)
{
int i;
// We can call foo() here before its definition because the
// prototype has already been declared, above!
i = foo();
printf("%d\n", i); // 3490
}
int foo(void) // This is the definition, just like the prototype!
{
return 3490;
}
अगर आप फ़ंक्शन को इस्तेमाल करने से पहले घोषित नहीं करते (प्रोटोटाइप या
उसकी परिभाषा के ज़रिए), तो आप इम्प्लिसिट डिक्लेरेशन[i[Implicit
declaration]] कर रहे होते हैं। पहले सी मानक (सी89) में इसकी अनुमति थी और
उस मानक में इसके नियम भी हैं, पर आज इसकी अनुमति नहीं है। और नए कोड में
इस पर निर्भर रहने की कोई जायज़ वजह नहीं है।
हमारे उदाहरण कोड में आपने एक बात देखी होगी... यही कि हम अच्छे पुराने
printf() फ़ंक्शन को बिना परिभाषित किए या प्रोटोटाइप घोषित किए इस्तेमाल करते
आ रहे हैं! इस अराजकता से हम बच कैसे जाते हैं? असल में हम बचते नहीं।
उसका प्रोटोटाइप मौजूद है; वह उस हेडर फ़ाइल stdio.h में है जिसे हमने
#include से शामिल किया था, याद है? तो हम अब भी क़ानूनी हैं, अफ़सर
साहब![i[Function prototypes]>]
खाली पैरामीटर सूचियाँ {#empty-parameter-lists}
[i[Empty parameter lists]]
सी23 में, फ़ंक्शन परिभाषा या प्रोटोटाइप में खाली पैरामीटर सूची का ठीक वही
मतलब है जो वहाँ void[i[void type]] लिखने का होता।
void foo();
void foo(void); // Same in C23
फिर भी, आजकल सी कोड में पैरामीटर सूची में स्पष्ट void[i[void type]]
ही पसंद किया जाता है, मुख्यतः इसलिए कि सी23 से पहले खाली पैरामीटर सूची का
व्यवहार काफ़ी अलग था। और अच्छे तरीके से नहीं।
हम अभी इसमें गहराई से जाएँगे, पर संक्षेप में बात यह है: अगर कोड कभी सी23
से पुराने कंपाइलर से बनेगा, तो सभी फ़ंक्शन घोषणाओं में खाली पैरामीटर सूची
दर्शाने के लिए हमेशाvoid[i[void type]] का इस्तेमाल करें।
दो संदर्भ हैं जिन पर विचार करना है।
फ़ंक्शन की परिभाषा में सारे पैरामीटर छोड़ देना
प्रोटोटाइप में सारे पैरामीटर छोड़ देना
पहले एक संभावित फ़ंक्शन परिभाषा देखें:
void foo() // Should really have a `void` in there
{
printf("Hello, world!\n");
}
स्पेक कहता है कि इसका मतलब है कि फ़ंक्शन foo() कोई पैरामीटर नहीं लेता,
फिर भी अगर आप ग़लती से कोई आर्ग्युमेंट दे दें, तो कंपाइलर को चेतावनी न
देने की छूट है। और ऐसा करने पर वह अनडिफ़ाइंड बिहेवियर है। इस सबसे बचने के
लिए बस void[i[void type]] लिख दें।
फ़ंक्शन प्रोटोटाइप के मामले में, void[i[void type-->in function
prototypes]] इस्तेमाल करने और न करने में बड़ा अंतर है:
void foo();
void foo(void); // Not the same!
प्रोटोटाइप में void छोड़ने का मतलब कंपाइलर को यह संकेत देना है कि फ़ंक्शन
के पैरामीटर के बारे में कोई अतिरिक्त जानकारी नहीं है। इससे टाइप-जाँच की
सारी व्यवस्था प्रभावी रूप से बंद हो जाती है, और void foo(); असल में
"प्रोटोटाइप" रह ही नहीं जाता। वह बस एक फ़ॉरवर्ड डिक्लेरेशन है।
तो प्रोटोटाइप में खाली पैरामीटर सूची हो, तो निश्चित रूप सेvoid
इस्तेमाल करें।
और वैसे भी, हर मामले में यह अच्छा अभ्यास है। शायद किसी दिन, जब सी23 से
पुराने सारे कंपाइलर चलन से बाहर हो जाएँ, तब हम इसे छोड़ सकेंगे।