सी, पायथन, जावास्क्रिप्ट, गो, रस्ट, सॉलिडिटी जैसी भाषाओं और कंप्यूटर साइंस के बुनियादी और गणित के उन्नत सिद्धांतों को हिंदी में पढ़ने के लिए संपर्क करें। हम वेबसाइट और सॉफ्टवेयर भी बनाते हैं।
"दुनिया बनाने के लिए हर तरह के लोग चाहिए, है न, पादरी?" "बिल्कुल, बेटा, बिल्कुल।"
---Pirate Captain Thomas Bartholomew Red, Padre से, फ़िल्म Pirates
C प्रोग्राम में सचमुच ढेरों चीज़ें हो सकती हैं।
हाँ जी।
और कई कारणों से हम सबके लिए आसान रहेगा अगर हम प्रोग्राम में मिलने वाली चीज़ों की कुछ क़िस्में तय कर लें, ताकि साफ़ रहे कि हम किस बारे में बात कर रहे हैं।
वेरिएबल
कहा जाता है कि "वेरिएबल मान रखते हैं"। लेकिन इसे सोचने का एक और तरीक़ा यह है कि वेरिएबल इंसान के पढ़ने लायक़ एक नाम है जो मेमोरी में रखे किसी डेटा की ओर इशारा करता है।
यहाँ हम एक पल रुककर पॉइंटरों वाली उस ख़रगोश की बिल में झाँकेंगे। चिंता मत कीजिए।
आप मेमोरी को बाइटों की एक बड़ी ऐरे की तरह सोच सकते हैं1। डेटा इसी "ऐरे" में रखा जाता है2। अगर कोई संख्या एक बाइट से बड़ी है, तो वह कई बाइटों में रखी जाती है। चूँकि मेमोरी ऐरे जैसी है, इसलिए मेमोरी के हर बाइट को उसके इंडेक्स से पुकारा जा सकता है। मेमोरी में इस इंडेक्स को पता (address), या स्थान (location), या पॉइंटर भी कहते हैं।
जब C में आपके पास कोई वेरिएबल होता है, तो उस वेरिएबल का मान मेमोरी में कहीं न कहीं, किसी पते पर होता है। ज़ाहिर है। आख़िर और कहाँ होगा? पर किसी मान को उसके संख्यात्मक पते से पुकारना सिरदर्द है, इसलिए हम उसके लिए एक नाम बना लेते हैं, और वेरिएबल वही है।
मैं यह सब इसलिए छेड़ रहा हूँ, इसकी दो वजहें हैं:
आगे चलकर पॉइंटर वेरिएबल समझना आसान हो जाएगा---वे ऐसे वेरिएबल हैं जो दूसरे वेरिएबलों का पता रखते हैं!
साथ ही, आगे चलकर पॉइंटर समझना आसान हो जाएगा।
तो वेरिएबल उस डेटा का नाम है जो मेमोरी में किसी पते पर रखा है।
वेरिएबल के नाम
वेरिएबल के नामों में आप 0-9, A-Z, a-z और अंडरस्कोर के दायरे का कोई भी अक्षर इस्तेमाल कर सकते हैं, इन नियमों के साथ:
वेरिएबल की शुरुआत अंक 0-9 से नहीं कर सकते।
वेरिएबल नाम की शुरुआत दो अंडरस्कोर से नहीं कर सकते।
वेरिएबल नाम की शुरुआत ऐसे अंडरस्कोर से नहीं कर सकते जिसके बाद कोई बड़ा अक्षर A-Z आए।
Unicode के लिए बस आज़माकर देख लीजिए। स्पेसिफ़िकेशन के §D.2 में कुछ नियम हैं जो बताते हैं कि आइडेंटिफ़ायर के किन हिस्सों में Unicode कोडपॉइंट की कौन-सी रेंज चल सकती है, पर वह यहाँ लिखने के लिए बहुत ज़्यादा है और शायद वह ऐसी चीज़ है जिसके बारे में आपको कभी सोचना भी नहीं पड़ेगा।
वेरिएबल के टाइप
आपके औज़ारों में पहले से कौन-सी भाषाएँ हैं, इस पर निर्भर करते हुए, हो सकता है आप टाइप की अवधारणा से परिचित हों या न हों। पर C इनके मामले में थोड़ी नकचढ़ी है, इसलिए एक बार दोहरा लेना ठीक रहेगा।
जब आप कहते हैं, तो C ज़्यादातर संख्यात्मक टाइपों के बीच अपने-आप बदलने की कोशिश करती है। पर इसके अलावा सारे रूपांतरण हाथ से करने पड़ते हैं, ख़ासकर स्ट्रिंग और संख्या के बीच।
C के लगभग सारे टाइप इन्हीं टाइपों की किस्में हैं।
किसी वेरिएबल को इस्तेमाल करने से पहले आपको उसे घोषित (declare) करना होगा और C को बताना होगा कि वेरिएबल किस टाइप का मान रखता है। एक बार घोषित हो जाने पर वेरिएबल का टाइप रनटाइम पर बाद में बदला नहीं जा सकता। आपने उसे जो बनाया, वह वही रहता है जब तक वह स्कोप से बाहर होकर ब्रह्मांड में वापस विलीन नहीं हो जाता।
चलिए अपना पिछला "Hello, world" कोड लें और उसमें कुछ वेरिएबल जोड़ें:
#include <stdio.h>
int main(void)
{
int i; // चिह्नित (signed) पूर्णांक रखता है, जैसे -3, -2, 0, 1, 10
float f; // चिह्नित फ़्लोटिंग पॉइंट संख्याएँ रखता है, जैसे -3.1416
printf("Hello, World!\n"); // आह, धन्य जानी-पहचानी चीज़
}
लीजिए! हमने कुछ वेरिएबल घोषित कर दिए। अभी तक हमने उन्हें इस्तेमाल नहीं किया है, और दोनों अनइनिशियलाइज़्ड हैं। एक पूर्णांक संख्या रखता है, और दूसरा फ़्लोटिंग पॉइंट संख्या (अगर आपकी गणित की पृष्ठभूमि है, तो मूल रूप से वास्तविक संख्या)।
अनइनिशियलाइज़्ड वेरिएबलों का मान अनिर्धारित (indeterminate) होता है5। उन्हें इनिशियलाइज़ करना ज़रूरी है, वरना आपको मानकर चलना होगा कि उनमें कोई बेतुकी संख्या पड़ी है।
यह उन जगहों में से एक है जहाँ C आपको "पकड़" सकती है। मेरे अनुभव में, ज़्यादातर बार अनिर्धारित मान शून्य होता है... पर वह हर रन में अलग हो सकता है! कभी मत मानिए कि मान शून्य होगा, भले ही आपको दिख रहा हो कि वह है। वेरिएबल को इस्तेमाल करने से पहले हमेशा साफ़-साफ़ किसी मान से इनिशियलाइज़ कीजिए6।
यह क्या? आप उन वेरिएबलों में कुछ संख्याएँ रखना चाहते हैं? पागलपन!
चलिए, वह भी कर डालते हैं:
int main(void)
{
int i;
i = 2; // वेरिएबल i में मान 2 डालो
printf("Hello, World!\n");
}
ज़बरदस्त। हमने एक मान रख दिया। अब इसे प्रिंट करते हैं।
हम यह printf() फ़ंक्शन को दो कमाल के आर्ग्युमेंट देकर करेंगे। पहला आर्ग्युमेंट एक स्ट्रिंग है जो बताती है कि क्या और कैसे प्रिंट करना है (इसे फ़ॉर्मैट स्ट्रिंग कहते हैं), और दूसरा वह मान है जिसे प्रिंट करना है, यानी वेरिएबल i में जो भी है।
printf() फ़ॉर्मैट स्ट्रिंग में तरह-तरह के ख़ास अनुक्रम खोजता है जो प्रतिशत चिह्न (%) से शुरू होते हैं और उसे बताते हैं कि क्या प्रिंट करना है। उदाहरण के लिए, अगर उसे %d मिलता है, तो वह अगला पास किया गया पैरामीटर देखता है, और उसे पूर्णांक के रूप में प्रिंट करता है। अगर उसे %f मिलता है, तो वह मान को float के रूप में प्रिंट करता है। अगर उसे %s मिलता है, तो वह स्ट्रिंग प्रिंट करता है।
इस तरह हम अलग-अलग टाइप के मान इस प्रकार प्रिंट कर सकते हैं:
#include <stdio.h>
int main(void)
{
int i = 2;
float f = 3.14;
char *s = "Hello, world!"; // char * ("char पॉइंटर") स्ट्रिंग का टाइप है
printf("%s i = %d and f = %f!\n", s, i, f);
}
और आउटपुट होगा:
Hello, world! i = 2 and f = 3.14!
इस तरह printf() उन दूसरी भाषाओं के तरह-तरह के फ़ॉर्मैट स्ट्रिंग या पैरामीटर वाली स्ट्रिंगों जैसा हो सकता है जिनसे आप परिचित हैं।
ध्यान दें: आगे के खंडों में मैं बस मान लेता हूँ कि आपने वेरिएबल पहले से घोषित कर रखे हैं। अगर उदाहरण में पूर्णांक i और j इस्तेमाल हुए हैं, तो बस मान लीजिए कि उदाहरण के ऊपर कहीं मैंने यह लिखा था:
int i, j;
बूलियन टाइप
क्या C में बूलियन टाइप हैं? सच या झूठ?
1!
ऐतिहासिक रूप से C में कोई बूलियन टाइप नहीं था, और कुछ लोग कह सकते हैं कि आज भी नहीं है।
C में 0 का मतलब "false" (असत्य) है, और शून्येतर किसी भी मान का मतलब "true" (सत्य)।
तो 1 सत्य है। और -37 सत्य है। और 0 असत्य है।
आप बूलियन टाइपों को बस int के रूप में घोषित कर सकते हैं:
int x = 1;
if (x) {
printf("x is true!\n");
}
C23 में आपको असली bool, true और false मिलते हैं। उससे पहले, अगर आपके पास C का काफ़ी आधुनिक संस्करण है, तो वही चीज़ पाने के लिए आप #include <stdbool.h> कर सकते हैं।
#include <stdio.h>
#include <stdbool.h> // C23 में ज़रूरी नहीं
int main(void) {
bool x = true;
if (x) {
printf("x is true!\n");
}
}
हालाँकि तकनीकी रूप से आपको bool वेरिएबल को true, false, या ऐसे किसी एक्सप्रेशन के नतीजे पर सेट करना चाहिए जो सत्य या असत्य निकलता है, पर असल में आप हर तरह की चीज़ को bool में बदल सकते हैं। कुछ ख़ास नियम हैं, पर शून्य जैसी चीज़ें आम तौर पर false निकलती हैं, और शून्येतर जैसी चीज़ें true।
पर मिलाते समय सावधान रहिए, क्योंकि true का संख्यात्मक मान शायद 1 होता है7, और अगर आप किसी और धनात्मक मान के true होने पर भरोसा कर रहे हैं, तो हो सकता है नतीजा बेमेल निकले। उदाहरण के लिए:
printf("%d\n", true == 12); // "0" प्रिंट करता है, यानी false!
ऑपरेटर और एक्सप्रेशन
C के ऑपरेटर आपको दूसरी भाषाओं से जाने-पहचाने लगने चाहिए। चलिए यहाँ उनमें से कुछ को फटाफट निपटा लेते हैं।
(इससे कहीं ज़्यादा ब्योरे हैं, पर शुरू करने के लिए हम इस खंड में बस उतना ही करेंगे जितना काफ़ी हो।)
अंकगणितीय
उम्मीद है ये जाने-पहचाने होंगे:
i = i + 3; // जोड़ (+) और असाइनमेंट (=) ऑपरेटर, i में 3 जोड़ो
i = i - 8; // घटाव, i में से 8 घटाओ
i = i * 9; // गुणा
i = i / 2; // भाग
i = i % 5; // मॉड्यूलो (भाग का शेषफल)
ऊपर के सभी के संक्षिप्त रूप भी होते हैं। उन पंक्तियों में से हर एक को ज़्यादा संक्षेप में इस तरह लिखा जा सकता है:
i += 3; // "i = i + 3" के बराबर, i में 3 जोड़ो
i -= 8; // "i = i - 8" के बराबर
i *= 9; // "i = i * 9" के बराबर
i /= 2; // "i = i / 2" के बराबर
i %= 5; // "i = i % 5" के बराबर
घातांक (exponentiation) का कोई ऑपरेटर नहीं है। आपको math.h के
pow() फ़ंक्शन के किसी रूप का इस्तेमाल करना होगा।
चलिए कुछ अजीब चीज़ों में उतरते हैं जो शायद आपकी दूसरी भाषाओं में न हों!
टर्नरी ऑपरेटर
C में टर्नरी ऑपरेटर भी है। यह ऐसा एक्सप्रेशन है जिसका मान उसमें जड़ी एक शर्त (conditional) के नतीजे पर निर्भर करता है।
// अगर x > 10 है, तो y में 17 जोड़ो। वरना y में 37 जोड़ो।
y += x > 10? 17: 37;
कितना गड़बड़झाला! जितना ज़्यादा पढ़ेंगे, उतनी आदत पड़ जाएगी। थोड़ी मदद के लिए मैं ऊपर के एक्सप्रेशन को if स्टेटमेंटों से दोबारा लिख देता हूँ:
// यह एक्सप्रेशन:
y += x > 10? 17: 37;
// इस नॉन-एक्सप्रेशन के बराबर है:
if (x > 10)
y += 17;
else
y += 37;
उन दोनों की तुलना तब तक कीजिए जब तक आपको टर्नरी ऑपरेटर का हर हिस्सा दिख न जाए।
या फिर एक और उदाहरण, जो प्रिंट करता है कि x में रखी संख्या विषम है या सम:
printf("The number %d is %s.\n", x, x % 2 == 0? "even": "odd");
printf() में %s फ़ॉर्मैट स्पेसिफ़ायर का मतलब है स्ट्रिंग प्रिंट करो। अगर एक्सप्रेशन x % 2 का नतीजा 0 आता है, तो पूरे टर्नरी एक्सप्रेशन का मान स्ट्रिंग "even" हो जाता है। वरना वह स्ट्रिंग "odd" हो जाता है। बहुत बढ़िया!
यह ध्यान रखना ज़रूरी है कि टर्नरी ऑपरेटर if स्टेटमेंट की तरह फ़्लो कंट्रोल नहीं है। यह बस एक ऐसा एक्सप्रेशन है जिसका नतीजा एक मान होता है।
प्री- और पोस्ट- इंक्रीमेंट और डिक्रीमेंट
अब चलिए एक और चीज़ से छेड़छाड़ करते हैं जो शायद आपने न देखी हो।
ये हैं मशहूर पोस्ट-इंक्रीमेंट और पोस्ट-डिक्रीमेंट ऑपरेटर:
i++; // i में एक जोड़ो (पोस्ट-इंक्रीमेंट)
i--; // i में से एक घटाओ (पोस्ट-डिक्रीमेंट)
बहुत आम तौर पर इन्हें बस इनके छोटे रूप की तरह इस्तेमाल किया जाता है:
i += 1; // i में एक जोड़ो
i -= 1; // i में से एक घटाओ
पर ये इससे कहीं बारीक ढंग से अलग हैं, ये चालाक बदमाश।
अब इसका दूसरा रूप देखते हैं, प्री-इंक्रीमेंट और प्री-डिक्रीमेंट:
++i; // i में एक जोड़ो (प्री-इंक्रीमेंट)
--i; // i में से एक घटाओ (प्री-डिक्रीमेंट)
प्री-इंक्रीमेंट और प्री-डिक्रीमेंट में वेरिएबल का मान एक्सप्रेशन का मूल्यांकन होने से पहले बढ़ाया या घटाया जाता है। फिर एक्सप्रेशन का मूल्यांकन नए मान के साथ होता है।
पोस्ट-इंक्रीमेंट और पोस्ट-डिक्रीमेंट में एक्सप्रेशन का मान पहले मौजूदा मान के साथ निकाला जाता है, और एक्सप्रेशन का मान तय हो जाने के बाद वेरिएबल का मान बढ़ाया या घटाया जाता है।
आप इन्हें असल में एक्सप्रेशनों के अंदर भी जड़ सकते हैं, ऐसे:
i = 10;
j = 5 + i++; // 5 + i निकालो, _उसके बाद_ i बढ़ाओ
printf("%d, %d\n", i, j); // 11, 15 प्रिंट करता है
अब इसकी तुलना प्री-इंक्रीमेंट ऑपरेटर से करते हैं:
i = 10;
j = 5 + ++i; // i बढ़ाओ, _उसके बाद_ 5 + i निकालो
printf("%d, %d\n", i, j); // 11, 16 प्रिंट करता है
यह तकनीक ऐरे और पॉइंटर को एक्सेस करने और उनमें हेरफेर करने में अक्सर इस्तेमाल होती है। यह आपको किसी वेरिएबल के मान को इस्तेमाल करने का, और उस मान को इस्तेमाल से पहले या बाद में बढ़ाने या घटाने का, एक तरीक़ा देती है।
पर आप इसे सबसे ज़्यादा for लूप में देखेंगे:
for (i = 0; i < 10; i++)
printf("i is %d\n", i);
पर इस पर आगे और बात होगी।
कॉमा ऑपरेटर
यह एक्सप्रेशनों को अलग करने का कम इस्तेमाल होने वाला तरीक़ा है, जिनमें एक्सप्रेशन बाएँ से दाएँ चलते हैं:
x = 10, y = 20; // पहले x को 10 दो, फिर y को 20
थोड़ा बेतुका लगता है, क्योंकि आप कॉमा की जगह बस सेमीकोलन लगा सकते थे, है न?
x = 10; y = 20; // पहले x को 10 दो, फिर y को 20
पर यह थोड़ा अलग है। बाद वाला दो अलग-अलग एक्सप्रेशन है, जबकि पहले वाला एक ही एक्सप्रेशन है!
कॉमा ऑपरेटर में कॉमा वाले एक्सप्रेशन का मान सबसे दाएँ वाले एक्सप्रेशन का मान होता है:
x = (1, 2, 3);
printf("x is %d\n", x); // 3 प्रिंट करता है, क्योंकि कॉमा वाली सूची में 3 सबसे दाएँ है
पर यह भी काफ़ी गढ़ा हुआ है। कॉमा ऑपरेटर की एक आम जगह for लूप है, जहाँ स्टेटमेंट के हर हिस्से में एक से ज़्यादा काम किए जाते हैं:
for (i = 0, j = 10; i < 100; i++, j++)
printf("%d, %d\n", i, j);
इस पर हम बाद में लौटेंगे।
कंडीशनल ऑपरेटर
बूलियन मानों के लिए हमारे पास मानक ऑपरेटरों की पूरी फ़ौज है:
a == b; // true अगर a, b के बराबर है
a != b; // true अगर a, b के बराबर नहीं है
a < b; // true अगर a, b से छोटा है
a > b; // true अगर a, b से बड़ा है
a <= b; // true अगर a, b से छोटा या उसके बराबर है
a >= b; // true अगर a, b से बड़ा या उसके बराबर है
असाइनमेंट (=) को तुलना (==) से मत मिलाइए! तुलना के लिए दो बराबर चिह्न इस्तेमाल करें, असाइन करने के लिए एक।
हम तुलना वाले एक्सप्रेशनों को if स्टेटमेंटों के साथ इस्तेमाल कर सकते हैं:
if (a <= 10)
printf("Success!\n");
बूलियन ऑपरेटर
हम बूलियन ऑपरेटरों से कंडीशनल एक्सप्रेशनों को जोड़ या बदल सकते हैं: और (and), या (or), और नहीं (not) के लिए।
| ऑपरेटर | बूलियन अर्थ |
| :----: | :---------: | --- | ------- |
| && | और (and) |
| | | | या (or) |
| ! | नहीं (not) |
बूलियन "and" का एक उदाहरण:
// अगर x 10 से छोटा और y 20 से बड़ा है तो कुछ करो:
if (x < 10 && y > 20)
printf("Doing something!\n");
बूलियन "not" का एक उदाहरण:
if (!(x < 12))
printf("x is not less than 12\n");
! की प्राथमिकता (precedence) दूसरे बूलियन ऑपरेटरों से ज़्यादा है, इसलिए उस स्थिति में हमें कोष्ठकों का इस्तेमाल करना पड़ता है।
वैसे यह बिल्कुल वैसा ही है जैसे:
if (x >= 12)
printf("x is not less than 12\n");
पर मुझे उदाहरण चाहिए था!
sizeof ऑपरेटर
यह ऑपरेटर बताता है कि कोई ख़ास वेरिएबल या डेटा टाइप मेमोरी में कितना आकार (बाइटों में) घेरता है।
ज़्यादा सटीक रूप से, यह बताता है कि किसी ख़ास एक्सप्रेशन का टाइप (जो सिर्फ़ एक वेरिएबल भी हो सकता है) मेमोरी में कितना आकार (बाइटों में) घेरता है।
यह अलग-अलग सिस्टमों पर अलग हो सकता है, सिवाय char और उसके रूपों के (जो हमेशा 1 बाइट के होते हैं)।
और अभी यह शायद बहुत काम का न लगे, पर हम यहाँ-वहाँ इसका हवाला देते रहेंगे, इसलिए इसे समझ लेना ठीक है।
चूँकि यह किसी टाइप को रखने के लिए ज़रूरी बाइटों की संख्या निकालता है, आप सोच सकते हैं कि यह int लौटाएगा। या... चूँकि आकार ऋणात्मक नहीं हो सकता, शायद unsigned?
पर पता चलता है कि C में sizeof के लौटाए मान को दर्शाने के लिए एक ख़ास टाइप है। वह है size_t, जिसे "साइज़ टी" पढ़ा जाता है8। हम बस इतना जानते हैं कि यह एक अनसाइन्ड पूर्णांक टाइप है जो sizeof को दी जा सकने वाली किसी भी चीज़ का आकार बाइटों में रख सकता है।
size_t बहुत-सी अलग जगहों पर दिखता है जहाँ चीज़ों की गिनतियाँ पास की या लौटाई जाती हैं। इसे एक ऐसा मान समझिए जो गिनती को दर्शाता है।
आप किसी वेरिएबल या एक्सप्रेशन का sizeof ले सकते हैं:
int a = 999;
// %zu टाइप size_t के लिए फ़ॉर्मैट स्पेसिफ़ायर है
// अगर आपका कंपाइलर "z" वाले हिस्से पर आपत्ति करे, तो उसे हटा दें
printf("%zu\n", sizeof a); // मेरे सिस्टम पर 4 प्रिंट होता है
printf("%zu\n", sizeof(2 + 7)); // मेरे सिस्टम पर 4 प्रिंट होता है
printf("%zu\n", sizeof 3.14); // मेरे सिस्टम पर 8 प्रिंट होता है
// अगर आपको ऋणात्मक size_t मान प्रिंट करने हों, तो %zd इस्तेमाल करें
याद रखिए: यह एक्सप्रेशन के टाइप का बाइटों में आकार है, एक्सप्रेशन का ख़ुद का आकार नहीं। इसीलिए 2+7 का आकार a के आकार के बराबर है---दोनों int टाइप के हैं। इस संख्या 4 पर हम अगले ही कोड ब्लॉक में लौटेंगे...
...जहाँ हम देखेंगे कि आप किसी टाइप का भी sizeof ले सकते हैं (ध्यान दें कि टाइप के नाम के आसपास कोष्ठक ज़रूरी हैं, एक्सप्रेशन के उलट):
printf("%zu\n", sizeof(int)); // मेरे सिस्टम पर 4 प्रिंट होता है
printf("%zu\n", sizeof(char)); // सभी सिस्टमों पर 1 प्रिंट होता है
यह ध्यान रखना ज़रूरी है कि sizeof एक कंपाइल-टाइम ऑपरेशन है9। एक्सप्रेशन का नतीजा पूरी तरह कंपाइल के समय तय हो जाता है, रनटाइम पर नहीं।
आगे चलकर हम इसका इस्तेमाल करेंगे।
फ़्लो कंट्रोल
बूलियन सब ठीक हैं, पर प्रोग्राम का प्रवाह (flow) नियंत्रित न कर पाएँ तो ज़ाहिर है हम कहीं के नहीं। चलिए कई कंस्ट्रक्ट देखते हैं: if, for, while और do-while।
सबसे पहले, स्टेटमेंटों और स्टेटमेंटों के ब्लॉकों के बारे में आगे की ओर देखता एक आम नोट, आपके मोहल्ले के दोस्ताना C डेवलपर की ओर से:
if या while जैसे स्टेटमेंट के बाद आप या तो एक अकेला स्टेटमेंट रख सकते हैं जिसे चलाया जाए, या स्टेटमेंटों का एक ब्लॉक जिसके सारे स्टेटमेंट क्रम से चलें।
चलिए एक अकेले स्टेटमेंट से शुरू करें:
if (x == 10) printf("x is 10\n");
इसे कभी-कभी अलग पंक्ति में भी लिखा जाता है। (C में व्हाइटस्पेस ज़्यादातर बेमानी है---यह Python जैसा नहीं है।)
if (x == 10)
printf("x is 10\n");
पर अगर आप चाहते हैं कि शर्त की वजह से कई चीज़ें हों तो? आप घुँघराले कोष्ठकों से एक ब्लॉक या कंपाउंड स्टेटमेंट चिह्नित कर सकते हैं।
if (x == 10) {
printf("x is 10\n");
printf("And also this happens when x is 10\n");
}
घुँघराले कोष्ठकों को हमेशा इस्तेमाल करना, भले ही ज़रूरी न हों, एक बहुत आम शैली है:
if (x == 10) {
printf("x is 10\n");
}
कुछ डेवलपरों को लगता है कि इससे कोड पढ़ना आसान होता है और ऐसी गलतियों से बचाव होता है, जहाँ चीज़ें देखने में तो if ब्लॉक में लगती हैं, पर असल में होती नहीं हैं।
// गलती का बुरा उदाहरण
if (x == 10)
printf("This happens if x is 10\n");
printf("This happens ALWAYS\n"); // सरप्राइज़!! बिना शर्त!
while, for और बाक़ी लूप वाले कंस्ट्रक्ट ऊपर के उदाहरणों की तरह ही काम करते हैं। अगर आप लूप में या if के बाद कई चीज़ें करना चाहते हैं, तो उन्हें घुँघराले कोष्ठकों में लपेट दीजिए।
दूसरे शब्दों में, if अपने बाद की उस एक चीज़ को चलाएगा। और वह एक चीज़ कोई अकेला स्टेटमेंट भी हो सकती है और स्टेटमेंटों का ब्लॉक भी।
if-else स्टेटमेंट
हम कई उदाहरणों में पहले से if इस्तेमाल करते आ रहे हैं, क्योंकि संभावना है कि आपने इसे किसी भाषा में पहले देखा होगा, पर लीजिए एक और:
int i = 10;
if (i > 10) {
printf("Yes, i is greater than 10.\n");
printf("And this will also print if i is greater than 10.\n");
}
if (i <= 10) printf("i is less than or equal to 10.\n");
इस उदाहरण कोड में संदेश तब प्रिंट होगा जब i 10 से बड़ा हो, वरना निष्पादन अगली पंक्ति पर चलता रहेगा। if स्टेटमेंट के बाद के घुँघराले कोष्ठकों पर ध्यान दीजिए; अगर शर्त सही है, तो या तो if के ठीक बाद का पहला स्टेटमेंट या एक्सप्रेशन चलेगा, या फिर if के बाद घुँघराले कोष्ठकों में रखा कोड का समूह चलेगा। इस तरह का कोड ब्लॉक व्यवहार सभी स्टेटमेंटों में आम है।
और हाँ, चूँकि C इस मामले में मज़ेदार है, आप अपने if पर else क्लॉज़ लगाकर शर्त के false होने पर भी कुछ करवा सकते हैं:
int i = 99;
if (i == 10)
printf("i is 10!\n");
else {
printf("i is decidedly not 10.\n");
printf("Which irritates me a little, frankly.\n");
}
और आप इन्हें कैस्केड करके तरह-तरह की शर्तें भी जाँच सकते हैं, ऐसे:
int i = 99;
if (i == 10)
printf("i is 10!\n");
else if (i == 20)
printf("i is 20!\n");
else if (i == 99) {
printf("i is 99! My favorite\n");
printf("I can't tell you how happy I am.\n");
printf("Really.\n");
}
else
printf("i is some crazy number I've never heard of.\n");
हालाँकि अगर आप यही रास्ता पकड़ रहे हैं, तो संभावित रूप से बेहतर हल के लिए switch स्टेटमेंट ज़रूर देखिए। पेच यह है कि switch सिर्फ़ अचर संख्याओं के साथ बराबरी की तुलना में काम करता है। ऊपर का if-else कैस्केड असमानता, रेंज, वेरिएबल, या कंडीशनल एक्सप्रेशन में गढ़ी जा सकने वाली किसी भी और चीज़ को जाँच सकता है।
while स्टेटमेंट
while आपका आम, रोज़मर्रा का लूप कंस्ट्रक्ट है। जब तक कोई शर्त वाला एक्सप्रेशन true है, तब तक कोई काम करते रहो।
चलिए एक बनाते हैं!
// यह आउटपुट प्रिंट करो:
//
// i is now 0!
// i is now 1!
// [ 2 से 7 के बीच भी यही सब ]
// i is now 8!
// i is now 9!
int i = 0;
while (i < 10) {
printf("i is now %d!\n", i);
i++;
}
printf("All done!\n");
इससे आपको एक बुनियादी लूप मिल जाता है। C में for लूप भी है, जो उस उदाहरण के लिए ज़्यादा साफ़-सुथरा रहता।
while का एक काफ़ी आम इस्तेमाल अनंत लूपों के लिए है, जहाँ आप तब तक दोहराते रहते हैं जब तक शर्त true है:
while (1) {
printf("1 is always true, so this repeats forever.\n");
}
do-while स्टेटमेंट
तो अब जब while स्टेटमेंट हमारे क़ाबू में आ गया, चलिए उसके क़रीबी चचेरे भाई do-while को देखते हैं।
ये मूल रूप से एक जैसे हैं, सिवाय इसके कि अगर पहली बार में लूप की शर्त false है, तो do-while एक बार चलेगा, पर while बिल्कुल नहीं चलेगा। दूसरे शब्दों में, ब्लॉक चलाना है या नहीं, इसकी जाँच do-while में ब्लॉक के अंत में होती है। while में वह ब्लॉक की शुरुआत में होती है।
उदाहरण से देखते हैं:
// while स्टेटमेंट इस्तेमाल करते हुए:
i = 10;
// यह नहीं चलेगा क्योंकि i 10 से छोटा नहीं है:
while(i < 10) {
printf("while: i is %d\n", i);
i++;
}
// do-while स्टेटमेंट इस्तेमाल करते हुए:
i = 10;
// यह एक बार चलेगा, क्योंकि लूप की शर्त की जाँच लूप की बॉडी चलने के
// बाद ही होती है:
do {
printf("do-while: i is %d\n", i);
i++;
} while (i < 10);
printf("All done!\n");
ध्यान दीजिए कि दोनों ही मामलों में लूप की शर्त शुरू से ही false है। इसलिए while में लूप नाकाम हो जाता है, और उसके बाद का कोड ब्लॉक कभी नहीं चलता। लेकिन do-while में शर्त कोड ब्लॉक चलने के बाद जाँची जाती है, इसलिए वह कम से कम एक बार हमेशा चलता है। इस मामले में वह संदेश प्रिंट करता है, i बढ़ाता है, फिर शर्त में नाकाम होता है, और "All done!" आउटपुट पर आगे बढ़ जाता है।
कहानी की सीख यह है: अगर आप चाहते हैं कि लूप कम से कम एक बार चले, चाहे लूप की शर्त कुछ भी हो, तो do-while इस्तेमाल कीजिए।
ये सारे उदाहरण शायद for लूप से बेहतर बनते। चलिए कुछ कम निर्धारित-सा करें---तब तक दोहराओ जब तक कोई ख़ास रैंडम संख्या न निकल आए!
#include <stdio.h> // printf के लिए
#include <stdlib.h> // rand के लिए
int main(void)
{
int r;
do {
r = rand() % 100; // 0 से 99 के बीच की रैंडम संख्या लो
printf("%d\n", r);
} while (r != 37); // 37 निकलने तक दोहराओ
}
एक ज़रूरी बात: क्या आपने इसे एक से ज़्यादा बार चलाया? अगर चलाया, तो क्या आपने ग़ौर किया कि संख्याओं का वही क्रम बार-बार निकला? और बार-बार? और फिर बार-बार? इसकी वजह यह है कि rand() एक छद्म-रैंडम संख्या जनरेटर है, जिसे अलग क्रम बनाने के लिए किसी अलग संख्या से सीड (seed) करना पड़ता है। ज़्यादा ब्योरे के लिए
srand()
फ़ंक्शन देखिए।
for स्टेटमेंट
दुनिया के सबसे लोकप्रिय लूपों में से एक में आपका स्वागत है! for लूप!
अगर आपको पहले से पता है कि आप कितनी बार लूप चलाना चाहते हैं, तो यह बढ़िया लूप है।
आप वही काम सिर्फ़ while लूप से भी कर सकते हैं, पर for लूप कोड को ज़्यादा साफ़-सुथरा रखने में मदद कर सकता है।
ये रहे बराबर कोड के दो टुकड़े---देखिए कि for लूप बस एक ज़्यादा संक्षिप्त रूप है:
// 0 से 9 तक (दोनों सहित) संख्याएँ प्रिंट करो...
// while स्टेटमेंट से:
i = 0;
while (i < 10) {
printf("i is %d\n", i);
i++;
}
// for-लूप से बिल्कुल वही काम करो:
for (i = 0; i < 10; i++) {
printf("i is %d\n", i);
}
जी हाँ, दोस्तो---ये बिल्कुल एक ही काम करते हैं। पर आप देख सकते हैं कि for स्टेटमेंट थोड़ा ज़्यादा संक्षिप्त है और आँखों को भी भाता है। (JavaScript इस्तेमाल करने वाले इस मोड़ पर इसकी C वाली जड़ों को पूरी तरह पहचान लेंगे।)
यह तीन हिस्सों में बँटा है, जो सेमीकोलन से अलग किए गए हैं। पहला इनिशियलाइज़ेशन है, दूसरा लूप की शर्त है, और तीसरा वह है जो ब्लॉक के अंत में होना चाहिए अगर लूप की शर्त true है। ये तीनों हिस्से वैकल्पिक हैं।
for (इनिशियलाइज़ करो; यह true हो तो लूप चलाओ; हर लूप के बाद यह करो)
ध्यान दें कि अगर लूप की शर्त शुरू में ही false है, तो लूप एक बार भी नहीं चलेगा।
for-लूप का एक मज़ेदार तथ्य!
आप for लूप के हर हिस्से में एक से ज़्यादा काम करने के लिए कॉमा ऑपरेटर इस्तेमाल कर सकते हैं!
for (i = 0, j = 999; i < 10; i++, j--) {
printf("%d, %d\n", i, j);
}
एक खाली for हमेशा चलता रहेगा:
for(;;) { // "हमेशा के लिए"
printf("I will print this again and again and again\n" );
printf("for all eternity until the heat-death of the universe.\n");
printf("Or until you hit CTRL-C.\n");
}
switch स्टेटमेंट
आप किन भाषाओं से आ रहे हैं, इस पर निर्भर करते हुए, हो सकता है आप switch से परिचित हों या न हों, या हो सकता है C का संस्करण आपकी आदत से ज़्यादा बंदिशों वाला लगे। यह ऐसा स्टेटमेंट है जो किसी पूर्णांक एक्सप्रेशन के मान के हिसाब से तरह-तरह की कार्रवाइयाँ करने देता है।
मूल रूप से यह किसी एक्सप्रेशन का मूल्यांकन करके पूर्णांक मान निकालता है, और उस मान से मेल खाने वाले
case पर कूद जाता है। निष्पादन वहीं से आगे चलता है। अगर break स्टेटमेंट आ जाए, तो निष्पादन switch से बाहर कूद जाता है।
यह रहा एक उदाहरण, जहाँ बकरियों की दी गई संख्या के लिए हम मोटे अंदाज़े से छापते हैं कि वह कितनी बकरियाँ हैं।
#include <stdio.h>
int main(void)
{
int goat_count = 2;
switch (goat_count) {
case 0:
printf("You have no goats.\n");
break;
case 1:
printf("You have a singular goat.\n");
break;
case 2:
printf("You have a brace of goats.\n");
break;
default:
printf("You have a bona fide plethora of goats!\n");
break;
}
}
उस उदाहरण में switchcase 2 पर कूदेगा और वहीं से चलना शुरू करेगा। जब (अगर) उसे break मिलता है, तो वह switch से बाहर कूद जाता है।
साथ ही, आपको नीचे वहाँ default लेबल दिख सकता है। जब कोई भी केस मेल नहीं खाता, तो यही चलता है।
हर case, default समेत, वैकल्पिक है। और वे किसी भी क्रम में आ सकते हैं, पर default को (अगर हो तो) आख़िर में रखना ही असल में आम चलन है।
तो पूरी चीज़ if-else कैस्केड की तरह काम करती है:
if (goat_count == 0)
printf("You have no goats.\n");
else if (goat_count == 1)
printf("You have a singular goat.\n");
else if (goat_count == 2)
printf("You have a brace of goats.\n");
else
printf("You have a bona fide plethora of goats!\n");
कुछ अहम अंतरों के साथ:
सही कोड पर कूदने में switch अक्सर तेज़ होता है (हालाँकि स्पेसिफ़िकेशन ऐसी कोई गारंटी नहीं देता)।
if-else में < और >= जैसी संबंधपरक शर्तें, और फ़्लोटिंग पॉइंट तथा दूसरे टाइप भी चल सकते हैं, जबकि switch में नहीं।
switch की एक और सुघड़ बात है जो कभी-कभी दिखती है और काफ़ी दिलचस्प है: फ़ॉल थ्रू (fall through)।
याद है कि break हमें switch से बाहर कैसे कूदा देता है?
तो क्या होगा अगर हम breakन करें?
पता चलता है कि हम सीधे अगले case में चलते चले जाते हैं! डेमो!
switch (x) {
case 1:
printf("1\n");
// फ़ॉल थ्रू!
case 2:
printf("2\n");
break;
case 3:
printf("3\n");
break;
}
अगर x == 1 है, तो यह switch पहले case 1 पर पहुँचेगा, 1 प्रिंट करेगा, पर फिर कोड की अगली पंक्ति पर बढ़ जाएगा... जो 2 प्रिंट करती है!
और फिर, आख़िरकार, हमें break मिलता है और हम switch से बाहर कूद जाते हैं।
अगर x == 2 है, तो हम बस case 2 पर पहुँचते हैं, 2 प्रिंट करते हैं, और सामान्य ढंग से break कर देते हैं।
break का न होना फ़ॉल थ्रू कहलाता है।
अगर आप C23 से पहले के संस्करण में हैं, तो जहाँ आप जान-बूझकर फ़ॉल थ्रू करना चाहते हैं, वहाँ कोड में हमेशा एक कमेंट डालिए, जैसा मैंने ऊपर किया। इससे दूसरे प्रोग्रामरों को यह सोचना नहीं पड़ेगा कि क्या आपका ऐसा करने का इरादा था। GCC उन कमेंटों को पहचान लेगा और वार्निंग दबा देगा।
या आप GCC की एक ग़ैर-मानक एट्रिब्यूट इस्तेमाल कर सकते हैं, जो मुझे कभी पसंद नहीं आई क्योंकि वह बदसूरत है और मानक नहीं है, पर GCC और Clang दोनों उसे पहचानते हैं:
switch (x) {
case 1:
printf("1\n");
__attribute__((fallthrough));
case 2:
...
पर अब, आख़िरकार, C23 में एट्रिब्यूटों के ज़रिए यह करने का एक मानक तरीक़ा है:
switch (x) {
case 1:
printf("1\n");
[[fallthrough]];
case 2:
...
पर याद रखिए, C प्रोग्रामों में बग घुसने की आम जगहों में से यह एक है: अपने case में break डालना भूल जाना। अगर आप नहीं चाहते कि बस लुढ़कते हुए अगले केस में चले जाएँ, तो इसे करना ही पड़ेगा10।
पहले मैंने कहा था कि switch पूर्णांक टाइपों के साथ काम करता है---इसे ऐसा ही रहने दीजिए। उसमें फ़्लोटिंग पॉइंट या स्ट्रिंग टाइप इस्तेमाल मत कीजिए। यहाँ एक चोर-दरवाज़े जैसी बात यह है कि आप कैरेक्टर टाइप इस्तेमाल कर सकते हैं, क्योंकि वे ख़ुद चुपके से पूर्णांक ही हैं। इसलिए यह पूरी तरह स्वीकार्य है:
char c = 'b';
switch (c) {
case 'a':
printf("It's 'a'!\n");
break;
case 'b':
printf("It's 'b'!\n");
break;
case 'c':
printf("It's 'c'!\n");
break;
}
आख़िर में, आप switch में enum भी इस्तेमाल कर सकते हैं क्योंकि वे भी पूर्णांक टाइप हैं। पर इस पर और बात enum वाले अध्याय में होगी।
Footnotes
आम तौर पर "बाइट" 8-बिट की बाइनरी संख्या होती है। इसे एक ऐसा पूर्णांक समझिए जो सिर्फ़ 0 से 255 तक (दोनों सहित) के मान रख सकता है। तकनीकी रूप से, C बाइट में कितने भी बिट होने देती है, और अगर आप बिना किसी संदेह के 8-बिट की संख्या का ज़िक्र करना चाहते हैं तो आपको शब्द ऑक्टेट इस्तेमाल करना चाहिए। लेकिन जब तक आप कुछ और न बताएँ, प्रोग्रामर यही मान लेंगे कि "बाइट" कहने पर आपका मतलब 8-बिट से है। ↩
यहाँ मैं आधुनिक मेमोरी के काम करने के तरीक़े को बहुत ज़्यादा सरल बना रहा हूँ। पर यह मानसिक मॉडल काम करता है, इसलिए कृपया माफ़ कर दीजिए। ↩
यहाँ मैं थोड़ा झूठ बोल रहा हूँ। तकनीकी रूप से 3.14159 का टाइप double है, पर हम अभी वहाँ नहीं पहुँचे हैं और मैं चाहता हूँ कि आप float को "फ़्लोटिंग पॉइंट" से जोड़कर देखें, और C ख़ुशी-ख़ुशी उस टाइप को float में बदल लेगी। संक्षेप में, बाद तक इसकी चिंता मत कीजिए। ↩
इसे "pointer to a char" या "char pointer" पढ़ें। "Char" यानी character। हालाँकि मुझे कोई अध्ययन नहीं मिला, पर क़िस्सों के हिसाब से ज़्यादातर लोग इसे "char" (चार) बोलते हैं, कुछ लोग "car" (कार), और मुट्ठी भर लोग "care" (केयर) बोलते हैं। पॉइंटरों पर हम आगे और बात करेंगे। ↩
बोलचाल में हम कहते हैं कि उनके मान "रैंडम" होते हैं, पर वे सचमुच---या छद्म-सचमुच भी---रैंडम संख्याएँ नहीं होतीं। ↩
यह बात पूरे 100% सही नहीं है। जब हम स्टैटिक स्टोरेज ड्यूरेशन के बारे में सीखेंगे, तब आप पाएँगे कि कुछ वेरिएबल अपने-आप शून्य से इनिशियलाइज़ हो जाते हैं। पर सुरक्षित तरीक़ा यही है कि हमेशा उन्हें इनिशियलाइज़ करें। ↩
तकनीकी रूप से char का सिर्फ़ एक बिट bool को दर्शाने के लिए इस्तेमाल होता है, इसलिए वह शून्य या एक ही हो सकता है। सिवाय इसके कि char के बाक़ी (पैडिंग) बिटों में क्या जाएगा, यह अनिर्दिष्ट है। false के लिए तो वह निश्चित ही सारा शून्य होना चाहिए। पर true के लिए मैं निश्चित नहीं हूँ कि बाक़ी बिटों का सारा शून्य होना ज़रूरी है। ↩
वेरिएबल लेंथ ऐरे को छोड़कर---पर वह किसी और दिन की कहानी है। ↩
इसे इतना बड़ा ख़तरा माना गया कि Go प्रोग्रामिंग भाषा के डिज़ाइनरों ने break को डिफ़ॉल्ट बना दिया; अगर आप अगले केस में गिरना चाहते हैं तो आपको Go का fallthrough स्टेटमेंट साफ़ तौर पर इस्तेमाल करना पड़ता है। ↩