सारांश
एक std::string अक्षर नहीं, बाइट रखता है, और आज के टेक्स्ट में एक अक्षर एक से चार बाइट तक ले सकता है। यह पाठ उन तीन चीज़ों को अलग करता है जिन्हें रोज़मर्रा की भाषा मिला देती है: अमूर्त वर्ण, जिसे यूनिकोड कोड पॉइंट नाम की एक संख्या से पहचानता है; वे बाइट जिनसे UTF-8 उसे एन्कोड करता है; और वह आकृति जो स्क्रीन बनाती है। यह समझाता है कि UTF-8 एक कोड पॉइंट के बिट उसके बाइटों में कैसे बाँटता है, “नगर” नौ बाइट क्यों लेता है, और किसी स्ट्रिंग के वर्ण उसे डिकोड किए बिना कैसे गिने जाते हैं। फिर यह दिखाता है कि कौन-सी संक्रियाएँ टेक्स्ट को तोड़ती हैं (बाइट-दर-बाइट उलटना, बाइट-दर-बाइट बड़े अक्षरों में बदलना, किसी वर्ण के बीच में काटना), कौन-सी बिना किसी सावधानी के काम करती हैं (सहेजना, कॉपी करना, जोड़ना, खोजना, लिखना) और C++23 की मानक लाइब्रेरी क्या नहीं करती: ASCII के बाहर बड़े और छोटे अक्षर, भाषा के अनुसार वर्णानुक्रम, सामान्यीकरण।
अधिगम उद्देश्य
- समझाना कि UTF-8 एक कोड पॉइंट को एक से चार बाइट में कैसे एन्कोड करता है, और “ñ” के बाइटों को हाथ से डिकोड करना।
- गैर-ASCII अक्षरों वाली स्ट्रिंग का बाइटों में आकार पहले से बताना, जैसे
"नगर"(9) या"हलचल"(12)। - UTF-8 स्ट्रिंग के वर्ण उन बाइटों को गिनकर गिनना जो निरंतरता बाइट नहीं हैं, शर्त
(c & 0xC0) != 0x80से। - उन संक्रियाओं को पहचानना जो UTF-8 टेक्स्ट को तोड़ती हैं (बाइट-दर-बाइट उलटना, हर बाइट पर
std::toupper, किसी वर्ण के बीच में काटना) और हर स्थिति में तय करना कि क्या करना है।
वर्ण, कोड पॉइंट और बाइट
तीन स्तर
पाठ वर्ण, बूलियन, रूपांतरण और स्थिरांक ने char को एक बाइट के ऐसे पूर्णांक के रूप में प्रस्तुत किया जो ASCII तालिका के अनुसार अक्षर के रूप में लिखा जाता है, और चेताया कि 'ऱ' उसमें नहीं समाता; पाठ कंपाइल करना, चलाना और कॉन्फ़िगर करना ने दिखाया कि "नगर" नौ बाइट लेता है और उसे लिखने के लिए कंसोल तैयार किया। यह पाठ समझाता है कि ऐसा क्यों है। ASCII 128 वर्णों को, 0 से 127 तक, एक-एक संख्या देता है, जो सात बिट में समा जाती है: अंग्रेज़ी के अक्षर, अंक, विराम चिह्न और कुछ नियंत्रण कोड। लेकिन वास्तविक टेक्स्ट में देवनागरी, बलाघात-चिह्नों और डायरेसिस वाले अक्षर, यूनानी अक्षर, चीनी चित्राक्षर, गणितीय चिह्न और इमोजी होते हैं, जिनकी गिनती लाखों में है। इससे यह निकलता है कि पहले तो एक कहीं बड़ी तालिका चाहिए, और दूसरे, उसकी संख्याओं को बाइटों में लिखने का एक तरीका।
तीन स्तरों को अलग करना उचित है। पहला है अमूर्त वर्ण: “छोटा एन्ये” अक्षर, इससे स्वतंत्र कि उसे कैसे सहेजा या बनाया जाता है। यूनिकोड वह मानक है जो हर अमूर्त वर्ण को एक संख्या देता है, उसका कोड पॉइंट, जिसे U+ के बाद कम से कम चार हेक्साडेसिमल अंकों के साथ लिखा जाता है: “ñ” U+00F1 है (241), “A” U+0041 है (65, वही संख्या जो ASCII में है), “€” U+20AC है (8364)। कोड पॉइंट 0 से U+10FFFF तक जाते हैं, दस लाख से थोड़े अधिक। दूसरा स्तर है एन्कोडिंग: वह नियम जो हर कोड पॉइंट को बाइटों के एक अनुक्रम में बदलता है। UTF-8 वह एन्कोडिंग है जिसका उपयोग वेब, आज के सिस्टम और यह पाठ्यक्रम करते हैं। तीसरा स्तर है प्रदर्शन: वह आकृति जो फ़ॉन्ट स्क्रीन पर बनाता है, और जो वर्ण के बदले बिना एक टाइपफ़ेस से दूसरे में बदल सकती है।
यदि ऐसा है, तो कोई टेक्स्ट डेटा तब तक टेक्स्ट नहीं है जब तक उसकी एन्कोडिंग ज्ञात न हो: बाइट c3 b1 UTF-8 में “ñ” हैं, पर किसी पुराने कंसोल की तालिका से पढ़े जाएँ तो वे उससे असंबद्ध दो चिह्न हैं, और यही वह दोष है जिसका निदान करना पाठ 1.4 ने सिखाया। यही प्रश्न, दस्तावेज़ों के टाइपसेटिंग की दृष्टि से, पाठ्यक्रम “शैक्षणिक लेखन के लिए LaTeX” के पाठ “भाषाएँ और फ़ॉन्ट” में आता है।
बाइट देखना
किसी स्ट्रिंग के बाइट देखने के लिए उसे पार करना काफ़ी है: std::string और std::string_view char के अनुक्रम हैं, और रेंज पर चलने वाला for लूप उनके बाइट एक-एक करके देता है। उन्हें हेक्साडेसिमल में लिखने के लिए फ़ॉर्मैट विनिर्देश {:02x} काम आता है (दो हेक्साडेसिमल अंक, बाईं ओर शून्य के साथ)। जैसे पाठ 2.4 में {:d} के साथ, और मानक के उसी सुधार के कारण जो 2023 में अपनाया गया और GCC 15 में लागू है, {:02x} एक char को चिह्नरहित बाइट के रूप में लिखता है। यह नियम महत्त्वपूर्ण है, क्योंकि x86-64 पर GCC में char चिह्नयुक्त है: बाइट c3, int में बदलने पर, संख्या है, जिसे {:02x} -3d लिखता।
#include <print>#include <string_view>// किसी टेक्स्ट के बाइट।// पूर्व-शर्त: कोई नहीं।// पश्च-शर्त: एक पंक्ति लिखता है जिसमें text, बाइटों में उसका आकार और हर बाइट हेक्साडेसिमल में है।void printBytes(std::string_view text){ std::print("{:<4}{}:", text, text.size()); for (const char c : text) { std::print(" {:02x}", c); } std::println("");}int main(){ printBytes("A"); printBytes("ñ"); printBytes("€"); printBytes("😀"); printBytes("नगर");}A 1: 41ñ 2: c3 b1€ 3: e2 82 ac😀 4: f0 9f 98 80नगर 9: e0 a4 a8 e0 a4 97 e0 a4 b0हर पंक्ति टेक्स्ट, उसका आकार और उसके बाइट दिखाती है। हर लंबाई का एक वर्ण: “A” एक बाइट लेता है, “ñ” दो, “€” तीन और इमोजी चार। शब्द “नगर” नौ बाइट लेता है, e0 a4 a8, e0 a4 97 और e0 a4 b0, और उसका size() 9 है, 3 नहीं। ध्यान दें कि “A” बाइट 41 है, वही जो ASCII में है: UTF-8 ASCII के 128 वर्णों को ठीक ASCII की तरह लिखता है।
चित्र “ñ” की एन्कोडिंग को ऊपर से नीचे तक दिखाता है: कोड पॉइंट, उसके बिट, उन बिटों का बँटवारा और परिणामी दो बाइट, जिनमें उपसर्ग के बिट उभारे गए हैं।
प्रमाण
“ñ” कोड पॉइंट
U+00F1है, अर्थात् ।बाइनरी में को
11110001लिखा जाता है। यह 127 से बड़ा और 2048 से छोटा है, इसलिए UTF-8 इसे दो बाइट में लिखता है, जो ग्यारह बिट ढोते हैं: ग्यारह अंक होने तक बाईं ओर शून्य भरकर00011110001मिलता है।ग्यारह बिट दो समूहों में बँटते हैं: पहले पाँच,
00011, और आख़िरी छह,110001।पहले बाइट में उपसर्ग
110होता है, जो दो बाइट वाले वर्ण की सूचना देता है, और उसके बाद पहले समूह के पाँच बिट:11000011। दूसरे में उपसर्ग10होता है, जो निरंतरता बाइट को चिह्नित करता है, और उसके बाद शेष छह बिट:10110001।हेक्साडेसिमल में
1100 0011c3है और1011 0001b1है: वही दो बाइट जो ऊपर वाले प्रोग्राम ने “ñ” के लिए लिखे। बाइनरी और हेक्साडेसिमल के बीच आधार का परिवर्तन, चार-चार बिट करके, पाठ्यक्रम “बीजगणित और ज्यामिति I” के पाठ “स्थानीय मान पर आधारित संख्यांकन पद्धतियाँ” वाला ही है।
सामग्री लोड हो रही है…