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


125

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

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

scanf("%c", &c);

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

scanf(" %c", &c);

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

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

जवाबों:


87

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

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

  • fgetcयदि आप केवल एक ही पढ़ रहे हैं, तो इसका उपयोग करना उपयोगी हो सकता है char

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

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

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

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

  • सादे एएनएसआई सी में सीमांकित-पृथक इनपुट को पार्स करने के लिए कोई अच्छा तरीका नहीं है। या तो strtok_rपोसिक्स से उपयोग करें या strtok, जो थ्रेड-सुरक्षित नहीं है। आप अपने स्वयं के थ्रेड-सुरक्षित संस्करण का भी उपयोग कर सकते हैं strcspnऔर strspn, के रूप मेंstrtok_r किसी विशेष ओएस समर्थन शामिल नहीं है।

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

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


जब से मैं वास्तव में में जाना नहीं था क्यों scanf मेरे सवाल में बुरा है, मैं विस्तार से बता देंगे:

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

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

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

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

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



56

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

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

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

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

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

ध्यान दें

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

यदि आप इनमें से बहुत कुछ करते हैं, तो मैं एक रैपर बनाने की सिफारिश कर सकता हूं जो हमेशा फ्लश करता है:

int printfflush (const char *format, ...)
{
   va_list arg;
   int done;
   va_start (arg, format);
   done = vfprintf (stdout, format, arg);
   fflush(stdout);
   va_end (arg);
   return done;
}```

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

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

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

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

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


ध्यान दें कि (r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2अनुगामी गैर-संख्यात्मक पाठ के रूप में खराब का पता नहीं चलता है।
chux -

1
@chux फिक्स्ड% f% f। आप पहले के साथ क्या मतलब है?
klutt

के साथ , इनपुट के साथ कुछ भी गलत नहीं बताता fgets()है "1 2 junk", if((r = sscanf(buffer, "%d%d", &x, &y)) != 2) {भले ही उसमें "जंक" हो।
chux -

@ अक्स आह, अब मैं देख रहा हूं। खैर यह जानबूझकर किया गया था।
klutt

1
scanfपूरी तरह से स्वरूपित डेटा के साथ उपयोग करने का इरादा है, लेकिन यह भी सच नहीं है। @ जंक के रूप में "जंक" के साथ समस्या के अलावा, यह भी तथ्य है कि एक प्रारूप की तरह "%d %d %d"एक, दो, या तीन लाइनों (या इससे भी अधिक, यदि रिक्त लाइनों में हस्तक्षेप कर रहे हैं) से इनपुट पढ़ने के लिए खुश है, तो यह नहीं है (दो) इनपुट को दो तरह से इनपुट करने के लिए बाध्य करने का तरीका "%d\n%d %d", आदि scanfस्वरूपित स्ट्रीम इनपुट के लिए उपयुक्त हो सकता है , लेकिन यह कुछ भी लाइन-आधारित के लिए अच्छा नहीं है।
स्टीव समिट

18

scanfजब आप जानते हैं कि आपका इनपुट हमेशा अच्छी तरह से संरचित और अच्छी तरह से व्यवहार किया जाता है, तो बहुत बढ़िया है । अन्यथा...

आईएमओ, यहां सबसे बड़ी समस्याएं हैं scanf :

  • बफर अतिप्रवाह का खतरा - आप के लिए एक क्षेत्र चौड़ाई निर्दिष्ट नहीं करते हैं, तो %sऔर%[ रूपांतरण विनिर्देशक, आप जोखिम एक बफर अतिप्रवाह (अधिक इनपुट को पढ़ने के लिए की तुलना में एक बफर पकड़ के लिए आकार है की कोशिश कर रहा)। दुर्भाग्य से, यह निर्दिष्ट करने का कोई अच्छा तरीका नहीं है कि एक तर्क के रूप में (जैसा कि printf) - आपको इसे रूपांतरण निर्दिष्टकर्ता के हिस्से के रूप में हार्डकोड करना होगा या कुछ मैक्रो शेंनिगन्स करना होगा।

  • उन इनपुट्स को स्वीकार करता है जिन्हें अस्वीकार कर दिया जाना चाहिए - यदि आप %dरूपांतरण विनिर्देशक के साथ एक इनपुट पढ़ रहे हैं और आप कुछ ऐसा टाइप करते हैं 12w4, तो आप उम्मीद करेंगे scanf उस इनपुट को अस्वीकार करने की , लेकिन ऐसा नहीं है - यह सफलतापूर्वक इनपुट स्ट्रीम में 12जा रहा है और असाइन करता है , w4अगले पढ़ने के लिए बेईमानी से।

तो, आपको इसके बजाय क्या उपयोग करना चाहिए?

मैं आमतौर पर सभी इंटरैक्टिव इनपुट को पाठ के रूप में पढ़ने की सलाह fgetsदेता हूं - यह आपको एक बार में पढ़ने के लिए अधिकतम वर्ण निर्दिष्ट करने की अनुमति देता है, जिससे आप बफर अतिप्रवाह को आसानी से रोक सकते हैं:

char input[100];
if ( !fgets( input, sizeof input, stdin ) )
{
  // error reading from input stream, handle as appropriate
}
else
{
  // process input buffer
}

एक विचित्र fgetsबात यह है कि यह कमरे में होने पर बफरिंग में अनुगामी न्यूलाइन को संग्रहित करेगा, इसलिए आप यह देखने के लिए एक आसान जांच कर सकते हैं कि क्या कोई व्यक्ति आपसे अधिक इनपुट में टाइप करता है, जिसकी आप अपेक्षा कर रहे थे:

char *newline = strchr( input, '\n' );
if ( !newline )
{
  // input longer than we expected
}

आप उसके साथ कैसे व्यवहार करते हैं - आप या तो पूरे इनपुट को हाथ से खारिज कर सकते हैं, और किसी भी शेष इनपुट को नीचे गिरा सकते हैं getchar:

while ( getchar() != '\n' ) 
  ; // empty loop

या आप अब तक मिले इनपुट को प्रोसेस कर सकते हैं और दोबारा पढ़ सकते हैं। यह उस समस्या पर निर्भर करता है जिसे आप हल करने की कोशिश कर रहे हैं।

इनपुट को टोकन करने के लिए (इसे एक या अधिक सीमांकक के आधार पर विभाजित करें), आप उपयोग कर सकते हैं strtok, लेकिन खबरदार - strtokइसके इनपुट को संशोधित करता है (यह स्ट्रिंग टर्मिनेटर के साथ सीमांकक को ओवरराइट करता है), और आप इसकी स्थिति (यानी, आप 'संरक्षित नहीं कर सकते) t एक स्ट्रिंग को आंशिक रूप से टोकेनाइज करें, फिर दूसरे को टोकेनाइज करना शुरू करें, फिर उठाएं जहां आपने मूल स्ट्रिंग में छोड़ा था)। एक संस्करण है, strtok_sजो टोकनधारक की स्थिति को संरक्षित करता है, लेकिन AFAIK इसका कार्यान्वयन वैकल्पिक है (आपको __STDC_LIB_EXT1__यह देखने के लिए परिभाषित करना होगा कि यह उपलब्ध है, यह देखने के लिए परिभाषित किया गया है)।

एक बार जब आप अपने इनपुट को टोकन कर लेते हैं, अगर आपको स्ट्रिंग्स को संख्याओं में बदलने की आवश्यकता होती है (यानी, "1234"=> 1234), तो आपके पास विकल्प हैं। strtolऔर strtodपूर्णांक और वास्तविक संख्या के स्ट्रिंग निरूपण को उनके संबंधित प्रकारों में परिवर्तित करेगा। वे आपको 12w4मेरे द्वारा बताए गए मुद्दे को पकड़ने की अनुमति भी देते हैं - उनके तर्कों में से एक स्ट्रिंग में परिवर्तित नहीं किए गए पहले वर्ण का सूचक है :

char *text = "12w4";
char *chk;
long val;
long tmp = strtol( text, &chk, 10 );
if ( !isspace( *chk ) && *chk != 0 )
  // input is not a valid integer string, reject the entire input
else
  val = tmp;

यदि आप फ़ील्ड की चौड़ाई निर्दिष्ट नहीं करते हैं ... - या रूपांतरण दमन (जैसे %*[%\n], जो उत्तर में बाद में लम्बी लाइनों से निपटने के लिए उपयोगी है)।
टोबी स्पाइट

फील्ड चौड़ाई के रन-टाइम विनिर्देश प्राप्त करने का एक तरीका है, लेकिन यह अच्छा नहीं है। आप अंत में अपने कोड में प्रारूप स्ट्रिंग का निर्माण कर रहे हैं (शायद का उपयोग करके snprintf()),।
टोबी स्पाईट

5
आपने isspace()वहां सबसे सामान्य गलती की है - यह निरूपित अहस्ताक्षरित वर्णों को स्वीकार करता है int, इसलिए आपको unsigned charउन प्लेटफार्मों पर यूबी से बचने के लिए डालने की आवश्यकता है जहां charहस्ताक्षर किए गए हैं।
टोबी स्पाइट

9

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

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

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

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

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

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

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

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इसलिए fgetsइसे ओवरफ्लो नहीं करना जानता है। लेकिन यह सुनिश्चित करने के लिए कि 512 सही संख्या है (विशेष रूप से, यह जांचने के लिए कि शायद किसी ने आकार बदलने के लिए कार्यक्रम को बदल दिया है), आपको जहां भी lineघोषित किया गया था वहां वापस पढ़ना होगा । यह एक उपद्रव है, इसलिए आकारों को सिंक में रखने के दो बेहतर तरीके हैं। आप (a) आकार के लिए नाम बनाने के लिए प्रीप्रोसेसर का उपयोग कर सकते हैं:

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

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

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

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

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"

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

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

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

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

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

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

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

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

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

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

strtok(line, "\n");

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

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

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

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

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

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

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

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

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

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


क्या sizeof (line)केवल लिखने के बजाय लिखने का एक अच्छा कारण है sizeof line? पूर्व जैसा दिखता है lineवह एक प्रकार का नाम है!
टोबी स्पाइट

@TobySpeight एक अच्छा कारण? नहीं, मुझे संदेह है। कोष्ठक मेरी आदत है, क्योंकि मुझे यह याद रखने की जहमत नहीं उठाई जा सकती कि यह उन वस्तुओं या प्रकार के नामों के लिए है जिनकी उन्हें आवश्यकता है, लेकिन कई प्रोग्रामर उन्हें छोड़ सकते हैं जब वे कर सकते हैं। (मेरे लिए यह व्यक्तिगत प्राथमिकता और शैली की बात है, और उस पर बहुत मामूली है।)
स्टीव समिट

sscanfएक रूपांतरण इंजन के रूप में उपयोग करने के लिए +1 लेकिन एक अलग टूल के साथ इनपुट एकत्र करना (और संभवतः मालिश करना)। लेकिन शायद getlinetaht संदर्भ में ध्यान देने योग्य है।
dmckee --- पूर्व-संचालक बिल्ली का बच्चा

जब आप " fscanfवास्तविक उपद्रवों" के बारे में बात करते हैं , तो क्या आपका मतलब है fgets? और उपद्रव # 3 वास्तव में मुझे परेशान करता है, विशेष रूप से दिया गया है कि scanfवर्ण इनपुट की संख्या को वापस करने के बजाय बफर को एक बेकार सूचक लौटाता है (जो न्यूलाइन को अधिक क्लीनर से अलग करना होगा)।
सुपरकैट

1
आपकी sizeofशैली की व्याख्या के लिए धन्यवाद । मेरे लिए, यह याद रखना कि जब आप पैरेंस को आसान करते हैं: मुझे लगता है कि (type)बिना मूल्य के कलाकारों की तरह होना चाहिए (क्योंकि हम केवल इस प्रकार में रुचि रखते हैं)। एक और बात: आप कहते हैं कि strtok(line, "\n")हमेशा काम नहीं करता है, लेकिन जब यह नहीं हो सकता है तो यह स्पष्ट नहीं है। मैं अनुमान लगा रहा हूं कि आप उस मामले के बारे में सोच रहे हैं जहां लाइन बफर से अधिक लंबी थी, इसलिए हमारे पास कोई नई रेखा नहीं है, और strtok()अशक्त है? यह वास्तविक दया fgets()है कि हम अधिक उपयोगी मूल्य नहीं लौटाते हैं ताकि हम जान सकें कि न्यूलाइन है या नहीं।
टोबे स्पाइट

7

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

इसके बजाय scanf(some_format, ...), fgets()साथ विचार करें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.
  }

6

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

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

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

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

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

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

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

  • प्रमुख व्हाट्सएप और ट्रेलिंग व्हाट्सएप को अनदेखा किया जाएगा (उदाहरण के लिए)
    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()फ़ंक्शन कोड की कई सौ पंक्तियों को समाप्त करने की संभावना है।

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

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

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

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


5

यहां 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]

%%

[!@#%&*/].*\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);
}

-5

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


एक परिमित राज्य मशीन ओवरकिल हो सकती है; रूपांतरणों में अतिप्रवाह का पता लगाने के आसान तरीके (जैसे कि जाँच करने के errno == EOVERFLOWबाद यदि strtollसंभव हो)।
एसएस ऐनी

1
आप अपनी खुद की परिमित राज्य मशीन को क्यों कोड करेंगे, जब फ्लेक्स उन्हें तुच्छ रूप से सरल बनाता है?
jamesqf
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.