क्यों std :: getline () स्वरूपित निष्कर्षण के बाद इनपुट छोड़ें?


105

मेरे पास निम्नलिखित कोड का कोड है जो उपयोगकर्ता को उनके नाम और राज्य के लिए संकेत देता है:

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::cin >> name && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
}

मुझे लगता है कि नाम सफलतापूर्वक निकाला गया है, लेकिन राज्य नहीं। यहाँ इनपुट और परिणामी आउटपुट है:

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

उत्पादन से राज्य का नाम क्यों छोड़ दिया गया है? मैंने उचित इनपुट दिया है, लेकिन कोड किसी भी तरह इसे अनदेखा कर देता है। क्यों होता है ऐसा?


मेरा मानना ​​है std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)कि उम्मीद के मुताबिक काम भी करना चाहिए। (नीचे दिए गए उत्तरों के अतिरिक्त)।
17

जवाबों:


122

क्यों होता है ऐसा?

यह आपके द्वारा प्रदान किए गए इनपुट के साथ बहुत कम है, लेकिन डिफ़ॉल्ट व्यवहार के साथ std::getline()प्रदर्शित करता है। जब आपने नाम के लिए अपना इनपुट प्रदान किया ( std::cin >> name), तो आपने न केवल निम्नलिखित वर्णों को जमा किया, बल्कि एक निहित न्यूलाइन को धारा में जोड़ा गया:

"John\n"

जब आप चुनते हैं Enterया Returnटर्मिनल से सबमिट करते समय आपके इनपुट पर एक नई पंक्ति हमेशा जोड़ दी जाती है । इसका उपयोग अगली पंक्ति की ओर बढ़ने के लिए फाइलों में भी किया जाता है। न्यूलाइन को nameअगले I / O ऑपरेशन तक निष्कर्षण के बाद बफर में छोड़ दिया जाता है जहां इसे या तो छोड़ दिया जाता है या उपभोग किया जाता है। जब नियंत्रण का प्रवाह पहुंचता है std::getline(), तो नई रेखा को छोड़ दिया जाएगा, लेकिन इनपुट तुरंत बंद हो जाएगा। ऐसा होने का कारण यह है कि इस फ़ंक्शन की डिफ़ॉल्ट कार्यक्षमता यह निर्धारित करती है कि यह (यह एक पंक्ति को पढ़ने का प्रयास करता है और जब यह एक नई रूपरेखा पाता है तो रुक जाता है)।

क्योंकि यह अग्रणी न्यूलाइन आपके प्रोग्राम की अपेक्षित कार्यक्षमता को बाधित करता है, इसलिए यह इस प्रकार है कि इसे किसी भी तरह से हमारे अनदेखे को छोड़ देना चाहिए। एक विकल्प std::cin.ignore()पहले निष्कर्षण के बाद कॉल करना है । यह अगले उपलब्ध चरित्र को छोड़ देगा ताकि न्यूलाइन अब रास्ते में न रहे।

std::getline(std::cin.ignore(), state)

इन-डेप्थ स्पष्टीकरण:

यह std::getline()आपके द्वारा कहा जाने वाला अधिभार है :

template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
                                    std::basic_string<charT>& str )

इस फ़ंक्शन का एक और अधिभार प्रकार का एक सीमांकक लेता है charT। एक सीमांकक चरित्र एक ऐसा चरित्र है जो इनपुट के दृश्यों के बीच की सीमा का प्रतिनिधित्व करता है। यह विशेष रूप से अधिभार, input.widen('\n')डिफ़ॉल्ट रूप से न्यूलाइन वर्ण में सीमांकक सेट करता है क्योंकि एक की आपूर्ति नहीं की गई थी।

अब, ये कुछ स्थितियां हैं, जिससे std::getline()इनपुट समाप्त होता है:

  • यदि धारा ने वर्णों की अधिकतम राशि निकाली है, जो std::basic_string<charT>धारण कर सकती है
  • यदि अंत-फ़ाइल (ईओएफ) चरित्र पाया गया है
  • अगर सीमांकक पाया गया है

तीसरी स्थिति वह है जिससे हम निपट रहे हैं। इस प्रकार आपके इनपुट stateका प्रतिनिधित्व किया जाता है:

"John\nNew Hampshire"
     ^
     |
 next_pointer

जहां next_pointerअगले चरित्र को पार्स किया जाना है। चूंकि इनपुट अनुक्रम में अगली स्थिति में संग्रहीत चरित्र सीमांकक है, इसलिए std::getline()चुपचाप उस चरित्र को छोड़ देंगे, next_pointerअगले उपलब्ध चरित्र में वृद्धि , और इनपुट रोक देंगे । इसका मतलब यह है कि आपके द्वारा प्रदान किए गए बाकी अक्षर अभी भी अगले I / O ऑपरेशन के लिए बफर में बने हुए हैं। आप देखेंगे कि यदि आप लाइन में से एक और रीड करते हैं state, तो आपका निष्कर्षण सही परिणाम देगा क्योंकि अंतिम कॉल को std::getline()सीमांकित को खारिज कर दिया जाएगा।


आपने देखा होगा कि स्वरूपित इनपुट ऑपरेटर ( operator>>()) के साथ निकालने पर आप आमतौर पर इस समस्या में नहीं आते हैं । ऐसा इसलिए है क्योंकि इनपुट स्ट्रीम इनपुट के लिए व्हॉट्सएप को सीमांकक के रूप में उपयोग करते हैं और डिफ़ॉल्ट रूप से std::skipws1 मैनिपुलेटर सेट करते हैं। जब धारा स्वरूपित इनपुट करने के लिए धारा धारा से प्रमुख व्हाट्सएप को छोड़ देगी। 2

स्वरूपित इनपुट ऑपरेटरों के विपरीत, std::getline()एक है अस्वरूपित इनपुट समारोह। और सभी बिना सूचना वाले इनपुट फ़ंक्शंस में निम्नलिखित कोड कुछ हद तक आम हैं:

typename std::basic_istream<charT>::sentry ok(istream_object, true);

उपरोक्त एक संतरी वस्तु है, जो सभी मानक C ++ क्रियान्वयन में प्रारूपित / गैर-स्वरूपित I / O फ़ंक्शन में त्वरित है। संतरी वस्तुओं का उपयोग I / O के लिए स्ट्रीम तैयार करने और यह निर्धारित करने के लिए किया जाता है कि यह विफल स्थिति में है या नहीं। आपको केवल यह पता चलेगा कि अनियंत्रित इनपुट फ़ंक्शंस में, संतरी कंस्ट्रक्टर का दूसरा तर्क है true। उस तर्क का मतलब है कि इनपुट अनुक्रम की शुरुआत से अग्रणी व्हाट्सएप को नहीं छोड़ा जाएगा। यहाँ मानक से प्रासंगिक उद्धरण है [the27.7.2.1.3 / 2]:

 explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);

[...] यदि noskipwsशून्य है और is.flags() & ios_base::skipwsनॉनजेरो है, तो फंक्शन अर्क निकालता है और प्रत्येक वर्ण को तब तक हटाता है जब तक कि अगला उपलब्ध इनपुट चरित्र cएक व्हाट्सएप चरित्र है। [...]

चूंकि उपरोक्त स्थिति झूठी है, संतरी वस्तु व्हॉट्सएप को नहीं छोड़ेगी। इस फ़ंक्शन द्वारा इसका कारण noskipwsनिर्धारित किया trueगया है क्योंकि इसका std::getline()उद्देश्य कच्चे, बिना वर्ण वाले वर्णों को एक std::basic_string<charT>वस्तु में पढ़ना है ।


समाधान:

के इस व्यवहार को रोकने का कोई तरीका नहीं है std::getline()। आपको जो करना है वह नई लाइन को std::getline()चलाने से पहले अपने आप को छोड़ना होगा (लेकिन इसे फ़ॉर्मेट किए गए निष्कर्षण के बाद करें)। यह तब तक किया जा सकता है ignore(), जब तक कि हम एक नई नई लाइन तक पहुँचने के लिए शेष इनपुट का उपयोग न कर लें:

if (std::cin >> name &&
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
    std::getline(std::cin, state))
{ ... }

आपको <limits>उपयोग करने के लिए शामिल करना होगा std::numeric_limitsstd::basic_istream<...>::ignore()एक ऐसा फ़ंक्शन है जो वर्णों की एक निर्दिष्ट राशि को तब तक हटाता है जब तक कि वह या तो एक सीमांकक नहीं ढूंढता है या स्ट्रीम के अंत तक पहुंच जाता है ( ignore()यदि यह मिल जाए तो सीमांकक को भी हटा देता है)। max()समारोह अक्षर हैं जो एक धारा स्वीकार कर सकते हैं की सबसे बड़ी राशि देता है।

व्हाट्सएप को त्यागने का एक और तरीका यह है कि उस std::wsफ़ंक्शन का उपयोग किया जाए जो इनपुट स्ट्रीम की शुरुआत से प्रमुख व्हाट्सएप को निकालने और छोड़ने के लिए डिज़ाइन किया गया है:

if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }

क्या फर्क पड़ता है?

अंतर यह है कि ignore(std::streamsize count = 1, int_type delim = Traits::eof())3 अंधाधुंध वर्णों का तब तक निर्वहन करता है जब तक कि वह वर्णों को नहीं छोड़ता count, परिसीमनकर्ता (दूसरे तर्क द्वारा निर्दिष्ट delim) को पाता है या धारा के अंत को हिट करता है। std::wsका उपयोग केवल धारा की शुरुआत से व्हाट्सएप वर्णों को छोड़ने के लिए किया जाता है।

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


1: std::skipwsजोड़तोड़ है जो इनपुट स्ट्रीम को प्रमुख व्हाट्सएप को छोड़ने के लिए कहता है जब स्वरूपित इनपुट प्रदर्शन करते हैं। इसे std::noskipwsमैनिपुलेटर से बंद किया जा सकता है।

2: इनपुट कुछ वर्णों को डिफ़ॉल्ट रूप से व्हाट्सएप के रूप में परिभाषित करता है, जैसे कि स्पेस कैरेक्टर, न्यूलाइन कैरेक्टर, फॉर्म फीड, कैरेट रिटर्न इत्यादि।

3: इस पर हस्ताक्षर हैं std::basic_istream<...>::ignore()। आप इसे स्ट्रीम से किसी एक वर्ण को छोड़ने के लिए शून्य तर्क के साथ कह सकते हैं, एक तर्क कुछ वर्णों को छोड़ने के लिए, या दो तर्क को countवर्णों को छोड़ने के लिए या जब तक यह पहुँचता है delim, जो भी पहले आता है। आप सामान्य std::numeric_limits<std::streamsize>::max()रूप से मान के रूप में उपयोग countकरते हैं यदि आप नहीं जानते कि परिसीमन से पहले कितने वर्ण हैं, लेकिन आप उन्हें वैसे भी त्यागना चाहते हैं।


1
बस क्यों नहीं if (getline(std::cin, name) && getline(std::cin, state))?
फ्रेड लार्सन

@FredLarson अच्छी बात है। हालांकि यह काम नहीं करेगा अगर पहली निकासी एक पूर्णांक या कुछ भी है जो एक स्ट्रिंग नहीं है।
0x499602D2

बेशक, यहाँ ऐसा नहीं है और एक ही चीज़ को दो अलग-अलग तरीकों से करने का कोई मतलब नहीं है। पूर्णांक के लिए आप एक स्ट्रिंग में लाइन प्राप्त कर सकते हैं और फिर उपयोग कर सकते हैं std::stoi(), लेकिन तब यह स्पष्ट नहीं है कि एक फायदा है। लेकिन मैं केवल std::getline()लाइन-ओरिएंटेड इनपुट के लिए उपयोग करना पसंद करता हूं और फिर जो कुछ भी समझ में आता है, उस लाइन को पार्स करने से निपटता है। मुझे लगता है कि यह कम त्रुटि वाला है।
फ्रेड लार्सन

@FredLarson सहमत। शायद मुझे लगता है कि अगर मैं समय है में जोड़ देंगे।
0x499602D2

1
@ एलबिन आप जिस कारण का उपयोग करना चाहते हैं std::getline()वह यह है कि यदि आप किसी दिए गए सीमांकक तक सभी वर्णों को कैप्चर करना चाहते हैं और इसे एक स्ट्रिंग में इनपुट करते हैं, तो डिफ़ॉल्ट रूप से यह नई लाइन है। यदि उन Xतारों की संख्या सिर्फ एक शब्द / टोकन हैं तो इस काम को आसानी से पूरा किया जा सकता है >>। अन्यथा आप पहले नंबर पर एक पूर्णांक में इनपुट करेंगे >>, cin.ignore()अगली पंक्ति पर कॉल करेंगे , और फिर एक लूप चलाएंगे जहाँ आप उपयोग करते हैं getline()
0x499602D2 18

11

यदि आप निम्नलिखित तरीके से अपना प्रारंभिक कोड बदलते हैं तो सब कुछ ठीक होगा:

if ((cin >> name).get() && std::getline(cin, state))

3
धन्यवाद। यह भी काम करेगा क्योंकि get()अगले चरित्र का उपभोग करता है। वहाँ भी है (std::cin >> name).ignore()जो मैंने अपने उत्तर में पहले सुझाया था।
0x499602D2

"..वर्क क्योंकि मिलता है () ..." हाँ, बिल्कुल। बिना विवरण के उत्तर देने के लिए क्षमा करें।
बोरिस

4
बस क्यों नहीं if (getline(std::cin, name) && getline(std::cin, state))?
फ्रेड लार्सन

0

ऐसा इसलिए होता है क्योंकि नई लाइन वर्ण के रूप में भी जानी जाने वाली एक अंतर्निहित रेखा फ़ीड \nएक टर्मिनल से सभी उपयोगकर्ता इनपुट से जुड़ी होती है क्योंकि यह धारा को एक नई लाइन शुरू करने के लिए कह रही है। std::getlineउपयोगकर्ता इनपुट की कई लाइनों की जांच करते समय आप सुरक्षित रूप से इसका उपयोग कर सकते हैं । इस स्ट्रीम में मौजूद इनपुट स्ट्रीम ऑब्जेक्ट से std::getlineन्यूलाइन कैरेक्टर \nतक का डिफ़ॉल्ट व्यवहार सब कुछ पढ़ेगा और उसमें शामिल होगा std::cin

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::getline(std::cin, name) && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
    return 0;
}
Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in New Hampshire"
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.