c - मैं स्कैनफ के बजाय इनपुट रूपांतरण के लिए क्या उपयोग कर सकता हूं?




scanf (6)

स्कैनफ के बदले इनपुट पार्स करने के लिए मैं क्या उपयोग कर सकता हूं?

scanf(some_format, ...) बजाय, sscanf(buffer, some_format_and %n, ...)

" %n" का उपयोग करके, कोड आसानी से पता लगा सकता है कि क्या सभी प्रारूप सफलतापूर्वक स्कैन किए गए थे और यह कि कोई अतिरिक्त गैर-सफेद-स्पेस जंक अंत में नहीं था।

// scanf("%d %f fred", &some_int, &some_float);
#define EXPECTED_LINE_MAX 100
char buffer[EXPECTED_LINE_MAX * 2];  // Suggest 2x, no real need to be stingy.

if (fgets(buffer, sizeof buffer, stdin)) {
  int n = 0;
  // add ------------->    " %n" 
  sscanf(buffer, "%d %f fred %n", &some_int, &some_float, &n);
  // Did scan complete, and to the end?
  if (n > 0 && buffer[n] == '\0') {
    // success, use `some_int, some_float`
  } else {
    ; // Report bad input and handle desired.
  }

मैंने बहुत बार देखा है कि लोग scanf का उपयोग करने से दूसरों को हतोत्साहित करते हैं और कहते हैं कि बेहतर विकल्प हैं। हालाँकि, मैं जो भी देख रहा हूं, वह या तो " scanf उपयोग नहीं करता है" या "यहां एक सही प्रारूप स्ट्रिंग है" , और "बेहतर विकल्प" का कोई भी उदाहरण कभी नहीं बताया गया है।

उदाहरण के लिए, आइए कोड का यह स्निपेट लें:

scanf("%c", &c);

यह व्हॉट्सएप को अंतिम रूपांतरण के बाद इनपुट स्ट्रीम में छोड़ दिया गया है। इसका उपयोग करने के लिए सामान्य रूप से सुझाया गया समाधान है:

scanf(" %c", &c);

या scanf उपयोग नहीं करने के लिए।

चूंकि scanf ख़राब है, इनपुट स्वरूपों को परिवर्तित करने के लिए कुछ एएनएसआई सी विकल्प क्या हैं जो scanf आमतौर पर scanf का उपयोग किए बिना (जैसे पूर्णांक, फ्लोटिंग-पॉइंट नंबर, और स्ट्रिंग्स) संभाल सकते हैं?


scanf ख़राब क्यों है?

मुख्य समस्या यह है कि scanf को कभी भी उपयोगकर्ता इनपुट से निपटने का इरादा नहीं था। यह "पूरी तरह से" स्वरूपित डेटा के साथ उपयोग करने का इरादा है। मैंने "पूरी तरह से" शब्द उद्धृत किया क्योंकि यह पूरी तरह से सच नहीं है। लेकिन यह डेटा को पार्स करने के लिए डिज़ाइन नहीं किया गया है जो उपयोगकर्ता इनपुट के रूप में अविश्वसनीय हैं। स्वभाव से, उपयोगकर्ता इनपुट अनुमानित नहीं है। उपयोगकर्ता निर्देशों को गलत समझते हैं, टाइपो बनाते हैं, गलती से प्रेस किए जाने से पहले दर्ज करते हैं आदि। एक व्यक्ति से यथोचित पूछ सकता है कि एक फ़ंक्शन जिसे उपयोगकर्ता इनपुट के लिए उपयोग नहीं किया जाना चाहिए stdin । यदि आप एक अनुभवी * निक्स उपयोगकर्ता हैं, तो स्पष्टीकरण आश्चर्य के रूप में नहीं आएगा, लेकिन यह विंडोज उपयोगकर्ताओं को भ्रमित कर सकता है। * एनआईएक्स सिस्टम में, पाइपिंग के माध्यम से काम करने वाले कार्यक्रमों का निर्माण करना बहुत आम है, जिसका अर्थ है कि आप पहले प्रोग्राम के stdin को दूसरे के stdin को पाइप करके एक प्रोग्राम को दूसरे में भेजते हैं। इस तरह, आप यह सुनिश्चित कर सकते हैं कि आउटपुट और इनपुट अनुमानित हैं। इन परिस्थितियों के दौरान, scanf वास्तव में अच्छी तरह से काम करता है। लेकिन अप्रत्याशित इनपुट के साथ काम करते समय, आप सभी प्रकार की परेशानी का जोखिम उठाते हैं।

तो उपयोगकर्ता इनपुट के लिए कोई आसान उपयोग मानक कार्य क्यों नहीं हैं? कोई केवल यहां अनुमान लगा सकता है, लेकिन मुझे लगता है कि पुराने कट्टर सी हैकर्स ने बस सोचा था कि मौजूदा फ़ंक्शन पर्याप्त रूप से अच्छे थे, भले ही वे बहुत क्लिस्की हों। इसके अलावा, जब आप विशिष्ट टर्मिनल अनुप्रयोगों को देखते हैं, तो वे बहुत कम ही stdin से उपयोगकर्ता इनपुट पढ़ते हैं। अधिकतर आप सभी उपयोगकर्ता इनपुट को कमांड लाइन तर्क के रूप में पास करते हैं। निश्चित रूप से, अपवाद हैं, लेकिन अधिकांश अनुप्रयोगों के लिए, उपयोगकर्ता इनपुट बहुत मामूली चीज है।

तो आप क्या कर सकते हैं?

मेरा पसंदीदा sscanf साथ संयोजन में sscanf । मैंने एक बार उस बारे में जवाब लिखा था, लेकिन मैं पूरा कोड फिर से पोस्ट करूंगा। यहाँ एक उदाहरण सभ्य (लेकिन सही नहीं) त्रुटि जाँच और पार्सिंग है। यह डीबगिंग उद्देश्यों के लिए पर्याप्त है।

ध्यान दें

मैं विशेष रूप से उपयोगकर्ता को एक ही लाइन पर दो अलग-अलग चीजों को इनपुट करने के लिए कहना पसंद नहीं करता। मैं केवल यही करता हूं कि जब वे स्वाभाविक रूप से एक-दूसरे के हों। उदाहरण के लिए printf("Enter the price in the format <dollars>.<cent>: ") और फिर sscanf(buffer "%d.%d", &dollar, &cent) । मैं कभी भी printf("Enter height and base of the triangle: ") जैसा कुछ नहीं करूंगा printf("Enter height and base of the triangle: ") । नीचे दिए गए fgets का उपयोग करने का मुख्य बिंदु यह सुनिश्चित करने के लिए इनपुटों को fgets करना है कि एक इनपुट अगले को प्रभावित नहीं करता है।

#define bsize 100

void error_function(const char *buffer, int no_conversions) {
        fprintf(stderr, "An error occurred. You entered:\n%s\n", buffer);
        fprintf(stderr, "%d successful conversions", no_conversions);
        exit(EXIT_FAILURE);
}

char c, buffer[bsize];
int x,y;
float f, g;
int r;

printf("Enter two integers: ");
fflush(stdout); // Make sure that the printf is executed before reading
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);

// Unless the input buffer was to small we can be sure that stdin is empty
// when we come here.
printf("Enter two floats: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) error_function(buffer, r);

// Reading single characters can be especially tricky if the input buffer
// is not emptied before. But since we're using fgets, we're safe.
printf("Enter a char: ");
fflush(stdout);
if(! fgets(buffer, bsize, stdin)) error_function(buffer, 0);
if((r = sscanf(buffer, "%c", &c)) != 1) error_function(buffer, r);

printf("You entered %d %d %f %c\n", x, y, f, c);

इस तरह से करना एक आम समस्या को खत्म कर देगा, जो कि अनुगामी न्यूलाइन है जो घोंसले के इनपुट के साथ गड़बड़ कर सकती है। लेकिन इसका एक और मुद्दा है, जो कि अगर लाइन bsize से bsize । आप देख सकते हैं कि if(buffer[strlen(buffer)-1] != '\n') । यदि आप नई लाइन को हटाना चाहते हैं, तो आप buffer[strcspn(buffer, "\n")] = 0

सामान्य तौर पर, मैं उपयोगकर्ता से कुछ अजीब प्रारूप में इनपुट दर्ज करने की उम्मीद नहीं करने की सलाह दूंगा जो आपको विभिन्न चर के लिए पार्स करना चाहिए। यदि आप चर height और width निर्दिष्ट करना चाहते हैं, तो एक ही समय में दोनों के लिए मत पूछिए। उपयोगकर्ता को उनके बीच में प्रवेश करने की अनुमति दें। साथ ही, यह दृष्टिकोण एक अर्थ में बहुत स्वाभाविक है। जब तक आप stdin नहीं करेंगे तब तक आपको stdin से इनपुट कभी नहीं मिलेगा, इसलिए हमेशा पूरी लाइन क्यों न पढ़ें? बेशक यह अभी भी मुद्दों को जन्म दे सकता है अगर लाइन बफर से अधिक लंबी है। क्या मुझे यह याद रखना है कि C में उपयोगकर्ता इनपुट क्लंकी है? :)

बफर से अधिक समय तक लाइनों के साथ समस्याओं से बचने के लिए आप एक फ़ंक्शन का उपयोग कर सकते हैं जो स्वचालित रूप से उपयुक्त आकार के बफर को आवंटित करता है, आप getline() उपयोग कर सकते हैं। दोष यह है कि आपको बाद में परिणाम को free करना होगा।

खेल को आगे बढ़ाते हुए

यदि आप उपयोगकर्ता इनपुट के साथ C में प्रोग्राम बनाने के बारे में गंभीर हैं, तो मैं ncurses जैसी लाइब्रेरी पर एक नज़र डालने की सलाह ncurses । क्योंकि तब आप कुछ टर्मिनल ग्राफिक्स के साथ एप्लिकेशन बनाना चाहते हैं। दुर्भाग्य से, यदि आप ऐसा करते हैं, तो आप कुछ पोर्टेबिलिटी खो देंगे, लेकिन यह आपको उपयोगकर्ता इनपुट का बेहतर नियंत्रण देता है। उदाहरण के लिए, यह आपको उपयोगकर्ता को प्रेस करने के लिए प्रतीक्षा करने के बजाय तुरंत एक प्रमुख प्रेस पढ़ने की सुविधा देता है।


इस उत्तर में मैं यह मानने जा रहा हूं कि आप पाठ की पंक्तियों को पढ़ रहे हैं और व्याख्या कर रहे हैं। शायद आप उपयोगकर्ता को संकेत दे रहे हैं, जो कुछ टाइप कर रहा है और RETURN को मार रहा है। या शायद आप किसी प्रकार की डेटा फ़ाइल से संरचित पाठ की पंक्तियाँ पढ़ रहे हैं।

चूंकि आप पाठ की पंक्तियों को पढ़ रहे हैं, इसलिए यह आपके कोड को लाइब्रेरी फ़ंक्शन के चारों ओर व्यवस्थित करने के लिए समझ में आता है जो पाठ को पढ़ता है, ठीक है, पाठ की एक पंक्ति। मानक फ़ंक्शन fgets() , हालांकि अन्य ( getline सहित) हैं। और फिर अगला कदम पाठ की उस पंक्ति को किसी तरह व्याख्यायित करना है।

पाठ की एक पंक्ति को पढ़ने के लिए कॉल करने का मूल नुस्खा यहां दिया गया है:

char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);

यह केवल पाठ की एक पंक्ति में पढ़ता है और इसे वापस प्रिंट करता है। जैसा कि लिखा गया है कि इसकी कुछ सीमाएँ हैं, जिन्हें हम एक मिनट में प्राप्त कर लेंगे। इसकी एक बहुत बड़ी विशेषता यह भी है: संख्या 512 जिसे हम दूसरे तर्क के रूप में पारित करते हैं, वह उस सरणी line का आकार है जिसे हम पढ़ने के लिए सुझाव दे रहे हैं। यह तथ्य - कि हम यह बता सकते हैं कि इसे पढ़ने की कितनी अनुमति है - इसका मतलब है कि हम यह सुनिश्चित कर सकते हैं कि इसमें बहुत अधिक पढ़कर भी सरणी को ओवरफ्लो नहीं किया जाएगा।

तो अब हम जानते हैं कि पाठ की एक पंक्ति कैसे पढ़ें, लेकिन क्या होगा यदि हम वास्तव में एक पूर्णांक, या एक फ्लोटिंग-पॉइंट नंबर, या एक एकल वर्ण या एक शब्द पढ़ना चाहते हैं? (यह है कि, क्या होगा अगर हम जिस scanf कॉल को बेहतर बनाने की कोशिश कर रहे हैं, वह %d , %f , %c , या %s जैसे प्रारूप विशेष का उपयोग कर रहा है?)

इन चीजों में से किसी एक के रूप में - एक स्ट्रिंग - पाठ की एक पंक्ति को फिर से व्याख्या करना आसान है। स्ट्रिंग को पूर्णांक में बदलने के लिए, सबसे सरल (हालांकि अपूर्ण) तरीका यह है कि atoi() को कॉल करें। फ़्लोटिंग-पॉइंट नंबर में बदलने के लिए, वहाँ `एटॉफ़ () है। (और भी बेहतर तरीके हैं, जैसा कि हम एक मिनट में देखेंगे।) यहाँ एक बहुत ही सरल उदाहरण दिया गया है:

printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);

यदि आप चाहते थे कि उपयोगकर्ता किसी एकल वर्ण को टाइप करे (शायद y या n को हां / नहीं प्रतिक्रिया के रूप में), तो आप शाब्दिक रूप से इस तरह से पंक्ति का पहला वर्ण पकड़ सकते हैं:

printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);

(यह ध्यान नहीं देता है, निश्चित रूप से, संभावना है कि उपयोगकर्ता ने एक बहु-वर्ण प्रतिक्रिया टाइप की है; यह चुपचाप टाइप किए गए किसी भी अतिरिक्त वर्ण की उपेक्षा करता है।)

अंत में, यदि आप चाहते थे कि उपयोगकर्ता एक स्ट्रिंग टाइप करे जिसमें निश्चित रूप से व्हाट्सएप न हो , यदि आप इनपुट लाइन का इलाज करना चाहते थे

hello world!

स्ट्रिंग के रूप में "hello" बाद कुछ और (जो कि scanf प्रारूप %s ने किया होगा), ठीक है, उस मामले में, मैंने थोड़ा सा फ़ाइब किया, इस तरह से लाइन को फिर से व्याख्या करना इतना आसान नहीं है, आखिर , इसलिए प्रश्न के उस भाग के उत्तर के लिए थोड़ा इंतजार करना होगा।

लेकिन पहले मैं तीन चीजों पर वापस जाना चाहता हूं जिन्हें मैंने छोड़ दिया।

(१) हम बुला रहे हैं

fgets(line, 512, stdin);

सरणी line में पढ़ने के लिए, और जहां 512 सरणी line का आकार है, इसलिए यह जानता है कि इसे अतिप्रवाह नहीं करना चाहिए। लेकिन यह सुनिश्चित करने के लिए कि 512 सही संख्या है (विशेष रूप से, यह जांचने के लिए कि शायद किसी ने आकार बदलने के लिए कार्यक्रम को बदल दिया है), आपको जहां भी line घोषित की गई थी, वहां वापस पढ़ना होगा। यह एक उपद्रव है, इसलिए आकारों को सिंक में रखने के दो बेहतर तरीके हैं। (ए) आकार के लिए एक नाम बनाने के लिए प्रीप्रोसेसर का उपयोग करें:

#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);

या, (बी) सी के sizeof ऑपरेटर का उपयोग करें:

fgets(line, sizeof(line), stdin);

(२) दूसरी समस्या यह है कि हम त्रुटि की जाँच नहीं कर रहे हैं। जब आप इनपुट पढ़ रहे होते हैं, तो आपको हमेशा त्रुटि की संभावना की जांच करनी चाहिए। यदि किसी भी कारण से आप पाठ की लाइन को पढ़ नहीं पाते हैं, तो यह अशक्त सूचक को वापस करके इंगित करता है। इसलिए हमें जैसी चीजें करनी चाहिए थीं

printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
    printf("Well, never mind, then.\n");
    exit(1);
}

अंत में, यह समस्या है कि पाठ की एक पंक्ति को पढ़ने के लिए, fgets पात्रों को पढ़ते हैं और उन्हें आपके सरणी में भरते हैं, जब तक कि यह \n वर्ण को समाप्त नहीं करता है, और यह आपके सरणी में \n वर्ण को भी भर देता है । आप इसे देख सकते हैं यदि आप हमारे पहले के उदाहरण को थोड़ा संशोधित करते हैं:

printf("you typed: \"%s\"\n", line);

अगर मैं इसे चलाता हूं और "स्टीव" टाइप करता हूं तो यह मुझे संकेत देता है, यह प्रिंट करता है

you typed: "Steve
"

यह " दूसरी पंक्ति पर है क्योंकि यह जिस स्ट्रिंग को पढ़ा और वापस प्रिंट किया गया वह वास्तव में "Steve\n"

कभी-कभी यह अतिरिक्त न्यूलाइन मायने नहीं रखती है (जैसे कि जब हम atof या atof , क्योंकि वे दोनों संख्या के बाद किसी भी अतिरिक्त गैर-संख्यात्मक इनपुट को अनदेखा करते हैं), लेकिन कभी-कभी यह बहुत मायने रखता है। इसलिए अक्सर हम उस न्यूलाइन को अलग करना चाहते हैं। ऐसा करने के कई तरीके हैं, जो मुझे एक मिनट में मिल जाएंगे। (मुझे पता है कि मैं बहुत कुछ कह रहा हूं। लेकिन मैं उन सभी चीजों को वापस पा लूंगा, मैं वादा करता हूं।)

इस बिंदु पर, आप सोच रहे होंगे: "मुझे लगा कि आपने कहा था कि scanf अच्छा नहीं था, और यह दूसरा तरीका इतना बेहतर होगा। लेकिन उपद्रव एक उपद्रव की तरह लगने लगे हैं। scanf को कॉल करना बहुत आसान था! मैं नहीं रख सकता! उसका इस्तेमाल कर रहे हैं?"

यदि आप चाहें तो ज़रूर, आप scanf का उपयोग कर सकते हैं। (और वास्तव में सरल चीजों के लिए, कुछ मायनों में यह सरल है।) लेकिन, कृपया, मेरे पास रोना मत आना जब यह 17 क्वर्की और फॉयबल्स में से एक के कारण आपको विफल कर देता है, या इनपुट के कारण एक अनंत लूप में चला जाता है उम्मीद नहीं की थी, या जब आप यह पता नहीं लगा सकते हैं कि कुछ अधिक जटिल करने के लिए इसका उपयोग कैसे करें। और आइए एक नज़र डालते हैं fgets के वास्तविक उपद्रवों पर:

  1. आपको हमेशा सरणी आकार निर्दिष्ट करना होगा। ठीक है, निश्चित रूप से, यह एक उपद्रव नहीं है - यह एक विशेषता है, क्योंकि बफर अतिप्रवाह एक बहुत बुरी बात है।

  2. आपको रिटर्न वैल्यू चेक करनी होगी। दरअसल, यह एक धो है, क्योंकि scanf सही ढंग से उपयोग करने के लिए, आपको इसके रिटर्न मूल्य की भी जांच करनी होगी।

  3. आपको \n बैक ऑफ को स्ट्रिप करना होगा। यह, मैं मानता हूं, एक सच्चा उपद्रव है। काश एक मानक कार्य होता जो मैं आपको बता सकता था कि यह छोटी समस्या नहीं थी। (कृपया कोई नहीं लाता gets ।) लेकिन scanf's 17 अलग-अलग उपद्रवों scanf's तुलना में, मैं किसी भी दिन fgets इस एक उपद्रव को लूंगा।

तो आप उस न्यूलाइन को कैसे स्ट्रिप करेंगे? तीन तरीके से:

(ए) स्पष्ट तरीका:

char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';

(बी) मुश्किल और कॉम्पैक्ट तरीका:

strtok(line, "\n");

दुर्भाग्य से यह एक हमेशा काम नहीं करता है।

(ग) एक और कॉम्पैक्ट और हल्का अस्पष्ट तरीका:

line[strcspn(line, "\n")] = '\0';

और अब जब कि यह रास्ते से बाहर है, तो हम एक और चीज़ पर वापस आ सकते हैं जिसे मैंने atoi() और atof() की खामियों पर छोड़ दिया था। उन लोगों के साथ समस्या यह है कि वे आपको सफलता या असफलता की सफलता का कोई उपयोगी संकेत नहीं देते हैं: वे चुपचाप गैर-इनपुट इनपुट की अनदेखी करते हैं, और वे चुपचाप वापस लौटते हैं यदि कोई संख्यात्मक इनपुट बिल्कुल नहीं है। पसंदीदा विकल्प - जिसमें कुछ अन्य फायदे भी हैं - strtod और strtodstrtol आपको 10 के अलावा एक आधार का उपयोग करने की अनुमति देता है, जिसका अर्थ है कि आप scanf साथ %o या %x (अन्य चीजों के बीच) का प्रभाव प्राप्त कर सकते हैं। लेकिन इन कार्यों को सही तरीके से कैसे उपयोग करना है, यह दिखाना अपने आप में एक कहानी है, और जो पहले से ही एक बहुत ही खंडित कथा में बदल रहा है, उससे बहुत अधिक विचलित हो जाएगा, इसलिए मैं अब उनके बारे में अधिक कुछ नहीं कहने जा रहा हूं।

मुख्य कथा चिंताओं के बाकी इनपुट आप पार्स करने की कोशिश कर रहे होंगे जो कि केवल एक संख्या या वर्ण से अधिक जटिल है। क्या होगा यदि आप दो संख्याओं वाली एक पंक्ति, या कई व्हाट्सएप-अलग-अलग शब्द, या विशिष्ट फ़्रेमिंग विराम चिह्न पढ़ना चाहते हैं? यहीं चीजें दिलचस्प हो जाती हैं, और जहां चीजें शायद जटिल हो रही थीं यदि आप scanf का उपयोग करके चीजों को करने की कोशिश कर रहे थे, और जहां अब बहुत अधिक विकल्प हैं, तो आपने fgets का fgets करके पाठ की एक पंक्ति को साफ-साफ पढ़ा है, हालांकि पूरी कहानी वे विकल्प शायद एक किताब भर सकते हैं, इसलिए हम केवल यहां सतह को खरोंचने में सक्षम होने जा रहे हैं।

  1. मेरी पसंदीदा तकनीक व्हॉट्सएप-अलग-अलग "शब्दों" में लाइन को तोड़ना है, फिर प्रत्येक "शब्द" के साथ आगे कुछ करें। ऐसा करने के लिए एक प्रमुख मानक कार्य strtok (जिसमें इसके मुद्दे भी हैं, और जो एक अलग चर्चा भी करता है)। मेरी अपनी प्राथमिकता प्रत्येक टूटे-फूटे "शब्द" के लिए एक व्यूह-रचना के लिए एक समर्पित फ़ंक्शन है, एक फ़ंक्शन जो मैं इन कोर्स नोट्स में वर्णित करता हूं। किसी भी दर पर, एक बार जब आप "शब्द" प्राप्त कर लेते हैं, तो आप हर एक को आगे संसाधित कर सकते हैं, शायद उसी atoi / atof / strtol / strtod फ़ंक्शन के साथ जो हमने पहले ही देखा है।

  2. विरोधाभासी रूप से, भले ही हम समय और प्रयास की एक उचित राशि खर्च कर रहे हैं, लेकिन यह पता लगा रहे हैं कि कैसे scanf से दूर जाना है, पाठ की लाइन से निपटने के लिए एक और बढ़िया तरीका जो हम अभी पढ़ते हैं, वह sscanf को पास करना है। इस तरह, आप scanf अधिकांश लाभों के साथ समाप्त होते हैं, लेकिन अधिकांश नुकसान के बिना।

  3. यदि आपका इनपुट सिंटैक्स विशेष रूप से जटिल है, तो इसे पार्स करने के लिए "रेगेक्सपी" लाइब्रेरी का उपयोग करना उचित हो सकता है।

  4. अंत में, आप जो भी तदर्थ पार्सिंग समाधान का उपयोग करते हैं, वह आपके अनुरूप होगा। आप एक समय में एक चरित्र के माध्यम से एक char * पॉइंटर की जांच कर सकते हैं जो आपके द्वारा अपेक्षित पात्रों के लिए है। या आप विशेष वर्णों के लिए खोज कर सकते हैं जैसे strchr या strrchr , या strspn या strcspn , या strpbrk । या आप strtod या strtod फ़ंक्शंस का उपयोग करके अंकों के समूहों को पार्स / कन्वर्ट और स्किप कर सकते हैं जिन्हें हमने पहले छोड़ दिया था।

स्पष्ट रूप से बहुत कुछ है जो कहा जा सकता है, लेकिन उम्मीद है कि यह परिचय आपको मिल जाएगा।


के रूप में पार्स करने की आवश्यकताओं को बताते हैं:

  • मान्य इनपुट स्वीकार किया जाना चाहिए (और किसी अन्य रूप में परिवर्तित)

  • अमान्य इनपुट अस्वीकार किया जाना चाहिए

  • जब किसी इनपुट को अस्वीकार कर दिया जाता है, तो उपयोगकर्ता को एक वर्णनात्मक संदेश प्रदान करना आवश्यक है जो बताता है (स्पष्ट रूप से "सामान्य लोगों द्वारा आसानी से समझा जाता है जो प्रोग्रामर नहीं हैं" भाषा) क्यों इसे अस्वीकार कर दिया गया था (ताकि लोग यह पता लगा सकें कि कैसे ठीक करना है मुसीबत)

चीजों को बहुत सरल रखने के लिए, एक एकल सरल दशमलव पूर्णांक को पार्स करने पर विचार करें (जो उपयोगकर्ता द्वारा टाइप किया गया था) और कुछ नहीं। उपयोगकर्ता के इनपुट को अस्वीकार किए जाने के संभावित कारण हैं:

  • इनपुट में अस्वीकार्य वर्ण थे
  • इनपुट एक संख्या को दर्शाता है जो स्वीकृत न्यूनतम से कम है
  • इनपुट एक संख्या का प्रतिनिधित्व करता है जो स्वीकृत अधिकतम से अधिक है
  • इनपुट एक संख्या का प्रतिनिधित्व करता है जिसमें एक गैर-शून्य आंशिक भाग होता है

आइए "इनपुट में निहित अस्वीकार्य वर्ण" को ठीक से परिभाषित करें; और कहते हैं कि:

  • प्रमुख व्हाट्सएप और ट्रेलिंग व्हाट्सएप को अनदेखा किया जाएगा (उदाहरण के लिए)
    5 "को" 5 "के रूप में माना जाएगा)
  • शून्य या एक दशमलव बिंदु की अनुमति है (उदाहरण के लिए "1234." और "1234.000" दोनों को "1234" के समान माना जाता है।
  • कम से कम एक अंक होना चाहिए (उदाहरण के लिए "" अस्वीकार कर दिया गया है)
  • एक से अधिक दशमलव बिंदु की अनुमति नहीं है (उदाहरण के लिए "1.2.3" अस्वीकृत)
  • अल्पविराम जो अंकों के बीच नहीं हैं उन्हें अस्वीकार कर दिया जाएगा (जैसे ", 1234" अस्वीकार कर दिया गया है)
  • एक दशमलव बिंदु के बाद आने वाले अल्पविराम को अस्वीकार कर दिया जाएगा (जैसे "1234.000,000" अस्वीकार कर दिया गया है)
  • अल्पविराम जो एक और अल्पविराम के बाद खारिज कर दिया जाता है (जैसे "1, 234" खारिज कर दिया जाता है)
  • अन्य सभी अल्पविरामों को अनदेखा किया जाएगा (उदाहरण के लिए "1,234" को "1234" माना जाएगा)
  • माइनस साइन जो कि पहले नॉन-व्हाट्सएप कैरेक्टर को खारिज नहीं किया गया है
  • एक सकारात्मक संकेत जो पहले गैर-व्हाट्सएप चरित्र को अस्वीकार नहीं किया गया है

इससे हम यह निर्धारित कर सकते हैं कि निम्न त्रुटि संदेशों की आवश्यकता है:

  • "इनपुट की शुरुआत में अज्ञात चरित्र"
  • "इनपुट के अंत में अज्ञात चरित्र"
  • "इनपुट के मध्य में अज्ञात चरित्र"
  • "संख्या बहुत कम है (न्यूनतम है ....)"
  • "संख्या बहुत अधिक है (अधिकतम है ....)"
  • "संख्या पूर्णांक नहीं है"
  • "बहुत अधिक दशमलव अंक"
  • "कोई दशमलव अंक नहीं"
  • "नंबर की शुरुआत में खराब कॉमा"
  • "संख्या के अंत में खराब कॉमा"
  • "संख्या के बीच में बुरा अल्पविराम"
  • "दशमलव के बाद खराब अल्पविराम"

इस बिंदु से हम देख सकते हैं कि एक स्ट्रिंग को पूर्णांक में बदलने के लिए एक उपयुक्त फ़ंक्शन को बहुत भिन्न प्रकार की त्रुटियों के बीच अंतर करने की आवश्यकता होगी; और यह कि " scanf() " या " atoi() " या " strtoll() " जैसी कोई चीज़ पूरी तरह से और पूरी तरह से बेकार है क्योंकि वे आपको इनपुट के साथ जो कुछ भी गलत था उसका कोई संकेत देने में विफल रहते हैं (और पूरी तरह से अप्रासंगिक और अनुचित परिभाषा का उपयोग करते हैं) क्या "वैध इनपुट" नहीं है)।

इसके बजाय, कुछ ऐसा लिखना शुरू करें जो बेकार न हो:

char *convertStringToInteger(int *outValue, char *string, int minValue, int maxValue) {
    return "Code not implemented yet!";
}

int main(int argc, char *argv[]) {
    char *errorString;
    int value;

    if(argc < 2) {
        printf("ERROR: No command line argument.\n");
        return EXIT_FAILURE;
    }
    errorString = convertStringToInteger(&value, argv[1], -10, 2000);
    if(errorString != NULL) {
        printf("ERROR: %s\n", errorString);
        return EXIT_FAILURE;
    }
    printf("SUCCESS: Your number is %d\n", value);
    return EXIT_SUCCESS;
}

बताई गई आवश्यकताओं को पूरा करने के लिए; इस convertStringToInteger() फ़ंक्शन को कोड की कई सौ पंक्तियों को समाप्त करने की संभावना है।

अब, यह केवल "एकल सरल दशमलव पूर्णांक पार्सिंग" था। सोचिए अगर आप कुछ जटिल करना चाहते हैं; "नाम, सड़क का पता, फोन नंबर, ईमेल पता" संरचनाओं की एक सूची की तरह; या शायद एक प्रोग्रामिंग भाषा की तरह। इन मामलों के लिए आपको एक अपंग बनाने के लिए कोड की हज़ारों पंक्तियाँ लिखने की आवश्यकता हो सकती है जो एक अपंग मजाक नहीं है।

दूसरे शब्दों में...

स्कैनफ के बदले इनपुट पार्स करने के लिए मैं क्या उपयोग कर सकता हूं?

अपनी आवश्यकताओं के अनुरूप स्वयं को कोड की (संभावित हजारों लाइनें) लिखें।


पढ़ने के इनपुट के सबसे आम तरीके हैं:

  • एक निश्चित आकार के साथ, जो आमतौर पर सुझाव दिया जाता है, और

  • fgetc का उपयोग fgetc , जो तब उपयोगी हो सकता है जब आप केवल एक ही fgetc पढ़ रहे हों।

इनपुट को परिवर्तित करने के लिए, कई प्रकार के फ़ंक्शन हैं जिनका आप उपयोग कर सकते हैं:

  • strtoll , एक स्ट्रिंग को पूर्णांक में बदलने के लिए

  • strtof / d / ld , एक स्ट्रिंग को फ्लोटिंग-पॉइंट नंबर में बदलने के लिए

  • sscanf , जो कि केवल scanf का उपयोग करते हुए उतना बुरा नहीं है, हालांकि इसमें नीचे बताए गए अधिकांश scanf हैं

  • सादे ANSI C. में सीमांकित-पृथक इनपुट को पार्स करने के लिए कोई अच्छा तरीका नहीं है। या तो POSIX से strtok_r उपयोग करें या नहीं-व्यापक रूप से लागू किए गए अनुलग्नक K से strspn का उपयोग करें । आप अपने स्वयं के strspn और strspn का उपयोग करके भी रोल कर सकते हैं, क्योंकि यह नहीं करता है किसी विशेष OS समर्थन को शामिल करें।

  • यह ओवरकिल हो सकता है, लेकिन आप उपयोगकर्ताओं को लेक्सर्स और पार्सर ( flex और bison सबसे आम उदाहरण हैं) कर सकते हैं।

  • कोई रूपांतरण नहीं, बस स्ट्रिंग का उपयोग करें

चूँकि आप अपने प्रश्न में scanf खराब क्यों हैं, इसलिए मैं इसमें नहीं गया, मैं विस्तृत हूँ:

  • रूपांतरण विनिर्देशक %[...] और %c , scanf नहीं खाता है। यह स्पष्ट रूप से व्यापक रूप से ज्ञात नहीं है, जैसा कि इस प्रश्न के कई डुप्लिकेट द्वारा दर्शाया गया है।

  • scanf के तर्कों (विशेष रूप से स्ट्रिंग्स के साथ) का उल्लेख करते समय यूनीरी & ऑपरेटर का उपयोग करने के बारे में कुछ भ्रम है।

  • scanf से रिटर्न वैल्यू को नजरअंदाज करना बहुत आसान है। यह आसानी से एक असंबद्ध चर को पढ़ने से अपरिभाषित व्यवहार का कारण बन सकता है।

  • scanf में बफर अतिप्रवाह को रोकने के लिए भूलना बहुत आसान है। scanf("%s", str) उतना ही बुरा है, जितना कि इससे भी बुरा, gets

  • जब आप पूर्णांक को scanf साथ परिवर्तित करते हैं, तो आप अतिप्रवाह का पता नहीं लगा सकते। वास्तव में, अतिप्रवाह इन कार्यों में अपरिभाषित व्यवहार का कारण बनता है।


यहां एक साधारण इनपुट को स्कैन करने के लिए flex का उपयोग करने का एक उदाहरण है, इस मामले में एएससीआईआई फ्लोटिंग पॉइंट नंबरों की एक फाइल जो यूएस ( n,nnn.dd ) या यूरोपीय ( n.nnn,dd ) प्रारूपों में हो सकती है। यह सिर्फ एक बहुत बड़े कार्यक्रम से कॉपी किया गया है, इसलिए कुछ अनसुलझे संदर्भ हो सकते हैं:

/* This scanner reads a file of numbers, expecting one number per line.  It  */
/* allows for the use of European-style comma as decimal point.              */

%{
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  #ifdef WINDOWS
    #include <io.h>
  #endif
  #include "Point.h"

  #define YY_NO_UNPUT
  #define YY_DECL int f_lex (double *val)

  double atofEuro (char *);
%}

%option prefix="f_"
%option nounput
%option noinput

EURONUM [-+]?[0-9]*[,]?[0-9]+([eE][+-]?[0-9]+)?
NUMBER  [-+]?[0-9]*[\.]?[0-9]+([eE][+-]?[0-9]+)?
WS      [ \t\x0d]

%%

[[email protected]#%&*/].*\n

^{WS}*{EURONUM}{WS}*  { *val = atofEuro (yytext); return (1); }
^{WS}*{NUMBER}{WS}*   { *val = atof (yytext); return (1); }

[\n]
.


%%

/*------------------------------------------------------------------------*/

int scan_f (FILE *in, double *vals, int max)
{
  double *val;
  int npts, rc;

  f_in = in;
  val  = vals;
  npts = 0;
  while (npts < max)
  {
    rc = f_lex (val);

    if (rc == 0)
      break;
    npts++;
    val++;
  }

  return (npts);
}

/*------------------------------------------------------------------------*/

int f_wrap ()
{
  return (1);
}




scanf