programmers
सी भाषा का भाग ६
स्ट्रिंग {#strings}
[i[Strings]<]आख़िरकार! स्ट्रिंग! इससे सरल और क्या हो सकता है? तो, पता चलता है कि सी में स्ट्रिंग असल में स्ट्रिंग होती ही नहीं! जी हाँ! वेपॉइंटर हैं! बेशक हैं! ऐरे की तरह ही, सी में स्ट्रिंग बस नाम भर के लिए मौजूद हैं। पर चलिए देखते हैं---यह वाक़ई इतनी बड़ी बात नहीं है।
स्ट्रिंग लिटरल {#string-literals}
[i[String literals]<]शुरू करने से पहले, चलिए सी में स्ट्रिंग लिटरल की बात करतेहैं। ये दोहरे उद्धरण-चिह्नों (") में बंद वर्णों के अनुक्रम हैं। (इकहरेउद्धरण-चिह्न वर्णों को घेरते हैं, और वह बिलकुल अलग जानवर है।)
उदाहरण:
{.c}"Hello, world!\n""This is a test.""When asked if this string had quotes in it, she replied, \"It does.\""
पहले में अंत में एक न्यूलाइन है---जो देखने में काफ़ी आम चीज़ है।
आख़िरी में उसके भीतर उद्धरण-चिह्न जड़े हैं, पर आप देख सकते हैं कि हर एक से पहलेएक बैकस्लैश (\) है (हम कहते हैं कि उसे बैकस्लैश से "एस्केप" किया गया है), जोबताता है कि इस जगह स्ट्रिंग में एक शाब्दिक उद्धरण-चिह्न होना चाहिए। इसी तरह सीकंपाइलर दोहरा उद्धरण-चिह्न प्रिंट करने और स्ट्रिंग के अंत वाले दोहरेउद्धरण-चिह्न के बीच का फ़र्क़ पहचान पाता है।[i[String literals]>]
स्ट्रिंग वेरिएबल {#string-variables}
[i[String variables]<]अब जब हमें स्ट्रिंग लिटरल बनाना आ गया है, तो चलिए उसेकिसी वेरिएबल में असाइन करते हैं ताकि हम उसके साथ कुछ कर सकें।
{.c}char *s = "Hello, world!";
उस टाइप को देखिए: char का पॉइंटर। स्ट्रिंग वेरिएबल s असल में उस स्ट्रिंगके पहले वर्ण का पॉइंटर है, यानी H का।
और हम उसे %s ("स्ट्रिंग" के लिए) फ़ॉर्मैट स्पेसिफ़ायर से प्रिंट कर सकते हैं:
printf("%s\n", s); // "Hello, world!"```
[i[String variables]>]
## ऐरे के रूप में स्ट्रिंग वेरिएबल {#string-variables-as-arrays}
[i[String variables-->as arrays]<]एक और विकल्प यह है, जो ऊपर के `char*` वाले प्रयोग के लगभग समतुल्य है:
```{.c}char s[14] = "Hello, world!";
// or, if we were properly lazy and have the compiler// figure the length for us:
char s[] = "Hello, world!";```
इसका मतलब है कि आप स्ट्रिंग के वर्णों तक पहुँचने के लिए ऐरे नोटेशन इस्तेमाल करसकते हैं। चलिए ठीक यही करके एक स्ट्रिंग के सारे वर्ण एक ही पंक्ति में प्रिंटकरते हैं:
```{.c .numberLines}#include <stdio.h>
int main(void){ char s[] = "Hello, world!";
for (int i = 0; i < 13; i++) printf("%c", s[i]); printf("\n");}```
ध्यान दीजिए कि हम एक अकेला वर्ण प्रिंट करने के लिए फ़ॉर्मैट स्पेसिफ़ायर `%c`इस्तेमाल कर रहे हैं।
साथ ही, यह भी देखिए। अगर हम `s` की परिभाषा बदलकर `char*` टाइप कर दें, तब भीप्रोग्राम ठीक चलेगा:
```{.c .numberLines}#include <stdio.h>
int main(void){ char *s = "Hello, world!"; // char* here
for (int i = 0; i < 13; i++) printf("%c", s[i]); // But still use arrays here...? printf("\n");}```
और प्रिंट करते समय हम अब भी काम पूरा करने के लिए ऐरे नोटेशन इस्तेमाल कर सकतेहैं! यह चौंकाने वाला है, पर यह भी सिर्फ़ इसलिए है कि हमने अभी तक ऐरे/पॉइंटरसमतुल्यता की बात नहीं की। पर यह एक और इशारा है कि ऐरे और पॉइंटर गहराई में एक हीचीज़ हैं।[i[String variables-->as arrays]>]
## स्ट्रिंग इनिशियलाइज़र {#string-initializers}
[i[Strings-->initializers]<]स्ट्रिंग वेरिएबल को स्ट्रिंग लिटरल से इनिशियलाइज़ करने के कुछ उदाहरण हम पहले हीदेख चुके हैं:
```{.c}char *s = "Hello, world!";char t[] = "Hello, again!";```
पर ये दोनों इनिशियलाइज़ेशन सूक्ष्म रूप से अलग हैं। स्ट्रिंग लिटरल की मेमोरी,पूर्णांक लिटरल की तरह, कंपाइलर आपके लिए अपने-आप सँभालता है! पूर्णांक के साथ,यानी निश्चित साइज़ के डेटा के साथ, कंपाइलर के लिए इसे सँभालना काफ़ी आसान है। परस्ट्रिंग बदलती-बाइट वाला जानवर है, जिसे कंपाइलर मेमोरी के एक टुकड़े में फेंककरऔर आपको उसका पॉइंटर देकर क़ाबू में करता है।
यह रूप वहीं इशारा करता है जहाँ वह स्ट्रिंग रखी गई थी। आम तौर पर वह जगह आपकेप्रोग्राम की बाक़ी मेमोरी से दूर किसी देश में होती है---रीड-ओनली मेमोरी---जिसकेकारण प्रदर्शन और सुरक्षा से जुड़े हैं।
```{.c}char *s = "Hello, world!";```
तो अगर आप उस स्ट्रिंग को इस तरह बदलने की कोशिश करें:
```{.c}char *s = "Hello, world!";
s[0] = 'z'; // BAD NEWS: tried to mutate a string literal!```
तो व्यवहार अनडिफ़ाइंड है। आपके सिस्टम के आधार पर, संभवतः क्रैश होगा।
पर उसे ऐरे के रूप में घोषित करना अलग है। कंपाइलर उन बाइट को शहर के किसी दूसरेहिस्से में नहीं रखता, वे ठीक सड़क के उस पार हैं। यह स्ट्रिंग की एक बदली जा सकनेवाली _कॉपी_ है---जिसे हम अपनी मर्ज़ी से बदल सकते हैं:
```{.c}char t[] = "Hello, again!"; // t is an array copy of the stringt[0] = 'z'; // No problem
printf("%s\n", t); // "zello, again!"```
तो याद रखिए: अगर आपके पास किसी स्ट्रिंग लिटरल का पॉइंटर है, तो उसे बदलने कीकोशिश मत कीजिए! और अगर आप किसी ऐरे को इनिशियलाइज़ करने के लिए दोहरेउद्धरण-चिह्नों वाली स्ट्रिंग इस्तेमाल करते हैं, तो वह असल में स्ट्रिंग लिटरलनहीं है।[i[Strings-->initializers]>]
## स्ट्रिंग की लंबाई पता करना {#getting-string-length}
[i[Strings-->getting the length]<]आप नहीं कर सकते, क्योंकि सी इसे आपके लिए दर्ज नहीं करती। और जब मैं कहता हूँ"नहीं कर सकते", तो असल में मेरा मतलब है "कर सकते हैं"^[हालाँकि यह सच है कि सीस्ट्रिंग की लंबाई दर्ज नहीं करती।]। `<string.h>` में `strlen()` नाम का एकफ़ंक्शन है जिससे किसी भी स्ट्रिंग की लंबाई बाइट में निकाली जा सकती है^[अगर आपबुनियादी वर्ण-समुच्चय या 8-बिट वर्ण-समुच्चय इस्तेमाल कर रहे हैं, तो आपको एक वर्णके एक बाइट होने की आदत है। पर यह सभी वर्ण-एन्कोडिंग में सच नहीं है।]।
```{.c .numberLines}#include <stdio.h>#include <string.h>
int main(void){ char *s = "Hello, world!";
printf("The string is %zu bytes long.\n", strlen(s));}```
`strlen()` फ़ंक्शन `size_t` टाइप लौटाता है, जो पूर्णांक टाइप है, इसलिए आप उसेपूर्णांक गणित में इस्तेमाल कर सकते हैं। हम `size_t` को `%zu` से प्रिंट करते हैं।
ऊपर का प्रोग्राम प्रिंट करता है:
```{.default}The string is 13 bytes long.```
बढ़िया! तो स्ट्रिंग की लंबाई पाना _संभव_ है![i[Strings-->getting the length]>]
पर... अगर सी स्ट्रिंग की लंबाई कहीं दर्ज नहीं करती, तो उसे कैसे पता चलता है किस्ट्रिंग कितनी लंबी है?
## स्ट्रिंग का समापन {#string-termination}
[i[Strings-->termination]<]सी स्ट्रिंग को कई प्रोग्रामिंग भाषाओं से थोड़ा अलग ढंग से सँभालती है, और असल मेंलगभग हर आधुनिक प्रोग्रामिंग भाषा से अलग ढंग से।
जब आप कोई नई भाषा बना रहे हों, तो मेमोरी में स्ट्रिंग रखने के मूलतः दो विकल्पहोते हैं:
1. स्ट्रिंग के बाइट के साथ एक संख्या भी रखें जो स्ट्रिंग की लंबाई बताए।
2. स्ट्रिंग के बाइट रखें, और स्ट्रिंग के अंत को एक विशेष बाइट से चिह्नित करें, जिसे _टर्मिनेटर_ कहते हैं।
अगर आप 255 से लंबी स्ट्रिंग चाहते हैं, तो विकल्प 1 में लंबाई रखने के लिए कम सेकम दो बाइट चाहिए। जबकि विकल्प 2 में स्ट्रिंग समाप्त करने के लिए सिर्फ़ एक बाइटचाहिए। तो वहाँ थोड़ी बचत है।
बेशक, आजकल एक बाइट (या 3---बहुत-सी भाषाएँ ख़ुशी-ख़ुशी आपको 4 गीगाबाइट लंबीस्ट्रिंग रखने देती हैं) बचाने की चिंता करना हास्यास्पद लगता है। पर पुराने ज़मानेमें यह बड़ी बात थी।
तो सी ने तरीक़ा #2 अपनाया। सी में "स्ट्रिंग" की परिभाषा दो बुनियादीविशेषताओं से होती है:
- स्ट्रिंग के पहले वर्ण का एक पॉइंटर।- पॉइंटर के बाद मेमोरी में कहीं एक शून्य-मान वाला बाइट (या `NUL` वर्ण^[यह `NULL` पॉइंटर से अलग है, और जब मैं वर्ण की बात करूँगा तो उसे `NUL` और पॉइंटर के लिए `NULL` लिखूँगा।]) जो स्ट्रिंग का अंत बताता है।
`NUL` वर्ण को सी कोड में `\0` लिखा जा सकता है, हालाँकि आपको अक्सर ऐसा करना नहींपड़ता।
जब आप अपने कोड में दोहरे उद्धरण-चिह्नों वाली स्ट्रिंग डालते हैं, तो `NUL` वर्णअपने-आप, अप्रत्यक्ष रूप से शामिल हो जाता है।
```{.c}char *s = "Hello!"; // Actually "Hello!\0" behind the scenes```
तो इसे ध्यान में रखते हुए, चलिए अपना ख़ुद का `strlen()` फ़ंक्शन लिखते हैं जोस्ट्रिंग में `char` तब तक गिनता है जब तक उसे `NUL` न मिल जाए।
तरीक़ा यह है कि स्ट्रिंग में आगे बढ़ते हुए एक `NUL` वर्ण ढूँढ़ें, और साथ-साथगिनते जाएँ^[आगे चलकर हम पॉइंटर अंकगणित से इसे करने का एक ज़्यादा साफ़-सुथरातरीक़ा सीखेंगे।]:
```{.c}int my_strlen(char *s){ int count = 0;
while (s[count] != '\0') // Single quotes for single char count++;
return count;}```
और मूलतः बिल्ट-इन `strlen()` भी इसी तरह काम पूरा करता है।[i[Strings-->termination]>]
## स्ट्रिंग की कॉपी बनाना {#copying-a-string}
[i[Strings-->copying]<]आप असाइनमेंट ऑपरेटर (`=`) से स्ट्रिंग की कॉपी नहीं बना सकते। वह बस पहले वर्ण केपॉइंटर की कॉपी बनाता है... तो आपके पास एक ही स्ट्रिंग के दो पॉइंटर रह जाते हैं:
```{.c .numberLines}#include <stdio.h>
int main(void){ char s[] = "Hello, world!"; char *t;
// This makes a copy of the pointer, not a copy of the string! t = s;
// We modify t t[0] = 'z';
// But printing s shows the modification! // Because t and s point to the same string!
printf("%s\n", s); // "zello, world!"}```
अगर आप स्ट्रिंग की कॉपी बनाना चाहते हैं, तो आपको उसे एक-एक बाइट करके कॉपी करनाहोगा---यानी आप स्ट्रिंग के अलग-अलग बाइट मेमोरी की एक जगह से लेंगे और उन्हेंमेमोरी में कहीं और दोहराएँगे। `strcpy()` फ़ंक्शन से यह आसान हो जाता है।[^440b]
[^440b]:`strncpy()` नाम का एक और फ़ंक्शन है जो कॉपी किए जाने वाले बाइट की संख्या सीमित करता है। कुछ लोग कहते हैं कि बफ़र ओवररन से सुरक्षा के कारण आपको हमेशा`strncpy()` इस्तेमाल करना चाहिए। दूसरे लोग कहते हैं कि आपको `strncpy()` कभी इस्तेमाल नहीं करना चाहिए, क्योंकि वह आपकी स्ट्रिंग को ज़रूरी नहीं कि समाप्त करे, जो एक और बदसूरत फ़ुटगन है। अगर आप सचमुच सुरक्षित रहना चाहते हैं, तो आप `strncpy()` का अपना संस्करण लिख सकते हैं जो स्ट्रिंग को हमेशा समाप्त करे।
स्ट्रिंग कॉपी करने से पहले पक्का कर लीजिए कि जिसमें कॉपी करनी है उसमें जगह है,यानी जिस गंतव्य ऐरे में वर्ण रखे जाएँगे, वह कम से कम उतना लंबा होना चाहिए जितनीलंबी स्ट्रिंग आप कॉपी कर रहे हैं।
```{.c .numberLines}#include <stdio.h>#include <string.h>
int main(void){ char s[] = "Hello, world!"; char t[100]; // Each char is one byte, so plenty of room
// This makes a copy of the string! strcpy(t, s);
// We modify t t[0] = 'z';
// And s remains unaffected because it's a different string printf("%s\n", s); // "Hello, world!"
// But t has been changed printf("%s\n", t); // "zello, world!"}```
ध्यान दीजिए कि `strcpy()` में गंतव्य पॉइंटर पहला आर्ग्युमेंट है, और स्रोत पॉइंटरदूसरा। इसे याद रखने के लिए मैं एक सूत्र इस्तेमाल करता हूँ: यह वही क्रम है जिसमेंआप `t` और `s` रखते अगर स्ट्रिंग के लिए असाइनमेंट `=` चलता, यानी स्रोत दाईं ओरऔर गंतव्य बाईं ओर।[i[Strings-->copying]>][i[Strings]>]
