How to use the getline command in c++?

前端 未结 5 1340
难免孤独
难免孤独 2021-02-03 14:22

I\'m trying to turn a cout command into a getline command in c++.

This is my code that I\'m trying to changes....

for (int count=0; count < numberOfEm         


        
5条回答
  •  予麋鹿
    予麋鹿 (楼主)
    2021-02-03 14:51

    Flushing Problems with cin.getline() in C++

    When you want to remove extraneous characters from an input stream in C++, it's usually because you mixed formatted and unformatted input methods. The formatted method would leave a newline in the stream and the unformatted method would consume it and terminate successfully, but fail completely to do what you wanted.

        #include   
     int main() {   
    std::cout<<"Enter the letter A: ";  
     std::cin.get();   
    std::cout<<"Enter the letter B: "; 
      std::cin.get();   
    std::cout<<"Too late, you can't type anymore\n";
     }
    

    Often this question stems from another question, which is how to pause a program before it terminates. Using cin.get() works only when there are no characters left in the stream. The instant you throw a cin>> foo; in the code, suddenly the solution fails. You need to clear out any leftover characters from the stream before it'll work again.

    So how do you fix the problem? The good news is that unless you want to get picky, it's as simple as a loop: C++ Syntax (Toggle Plain Text)

        #include   
     void ignore_line ( std::istream& in ) { 
      char ch;
            while ( in.get ( ch ) && ch != '\n' );
     } 
    

    This loop simply reads characters until end-of-file or a newline is read. It's generally assumed that interactive input in C++ is line-oriented and you're guaranteed to have a clean buffer after reading a newline. While that's not true (input doesn't have to be line-oriented), it's wide spread enough that we can assume it for the purposes of this thread.

    So what's wrong with this approach? Nothing. In fact, this is about as good as it gets unless you want to dig down and fix the subtle problems. But before we look at the problems, here's an alternative solution that does the same thing in a different way:

        #include 
        #include 
        #include    
    void ignore_line ( std::istream& in ) {   
    in.ignore ( std::numeric_limits::max(), '\n' );
     } 
    

    The ignore member function of std::istream will read and discard up to N characters or until a delimiter. In the above example, N is represented by the largest value of the streamsize data type, and the delimiter is a newline. It works equally well with just a large value (80 is common): C++ Syntax (Toggle Plain Text) in.ignore ( 80, '\n' ); However, the streamsize data type is more likely to be an accurate representation of the size of the buffer that the stream is using, and it's more likely to work all of the time. This is the solution that I recommend.

    So what's wrong with this? There are two notable problems. The first is easy to fix, and it stems from the fact that istream isn't very flexible. istream is actually a typedef for basic_istream. If you want a wide stream to work with ignore_line, you're SOL with istream. So the trick is to use basic_istream<> instead:

        #include 
        #include 
        #include    
    template  
    void ignore_line ( std::basic_istream& in ) { 
      in.ignore ( std::numeric_limits::max(), in.widen ( '\n' ) ); 
    }
    

    Now ignore_line is a template function that will derive the type of characters that the stream contains from the first argument. You can pass any stream that derives from or specializes basic_istream, and the problem is gone. Instead of just '\n', it's a good idea to use widen on the literal so that it will be properly converted to a wider type if necessary. Nice and easy.

    The second problem is harder. Much harder. It's harder because standard iostreams seem to block your way at every turn with lack of portability or undesired features. In fact, it's impossible to completely fix the problem. The problem is that the behavior is different depending on the contents of the stream. For example:

        #include 
        #include 
        #include 
        #include   
     template  
    void ignore_line ( std::basic_istream& in ) { 
      in.ignore ( std::numeric_limits::max(), in.widen ( '\n' ) );
     }  
     int main() {  
     std::cout<<"First input: "; 
      std::cin.get();  
     std::cout<<"Clearing cin.\n"; 
      std::cin.clear();   
    ignore_line ( std::cin ); 
      std::cout<<"All done.\n"; 
    } 
    

    Run this program three times:

    Input: "asdf" Output: The program finishes without any more input from you.

    Input: Just hit Enter Output: The program waits for you to hit Enter one more time.

    Input: Signal EOF Output: The program waits for you to hit Enter one more time.

    The problem is that the stream is empty. If you hit Enter immediately, a newline is placed on the stream and consumed by cin.get. Likewise with signaling EOF. At that point there's nothing left in the stream and cin.ignore stops everything until you type more characters. This is because cin.ignore is a blocking read. If there's nothing to read, it waits.

    What we'd like it to do is not block for any of those three cases. The good news is that the iostream library supports some possible solutions. The bad news is that these are dead ends. Here are two common ones:

    The sync member function The istream class supports a member function called sync. Why it has such a function is under debate, because nobody can agree on what it should be doing. Even Bjarne Stroustrup himself incorrectly stated that it discards all characters in the stream:

      #include   
     int main() {   
    std::cout<<"First input: ";  
     std::cin.get();   
    std::cout<<"Clearing cin.\n";  
     std::cin.clear();   
    std::cin.sync();   
    std::cout<<"All done.\n"; 
    } 
    

    When this works, it works beautifully. The bad news is that the C++ standard doesn't require sync to do anything like discarding extraneous characters. This solution is non-portable.

    The in_avail member function The next step is to look at the in_avail member function of istream's stream buffer. At first glance it looks like this member function will tell you how many characters are in the stream, and if it returns 0, you can refrain from calling ignore:

      #include 
        #include 
        #include 
        #include   
     template  
    void ignore_line ( std::basic_istream& in ) {
       if ( in.rdbuf()->in_avail() > 0 )
            in.ignore ( std::numeric_limits::max(), in.widen ( '\n' ) );
     }  
     int main() { 
      std::cout<<"First input: "; 
      std::cin.get();   
    std::cout<<"Clearing cin.\n"; 
      std::cin.clear();  
     ignore_line ( std::cin ); 
      std::cout<<"All done.\n";
     }
    

    As with sync, when this works, it works great. But once again, the standard raises a wall by saying that in_avail isn't required to give you an accurate representation of the characters in the stream. In fact, some popular implementations have a strictly conforming in_avail that always returns 0. Not very useful. Now we have to get creative.

    The putback member function

     #include 
        #include 
        #include 
        #include  
      template 
     void ignore_line
     ( std::basic_istream& in ) { 
      if ( !in.putback ( in.widen ( '\n' ) ) )
            in.ignore ( std::numeric_limits::max(), in.widen ( '\n' ) );   else
            in.ignore(); }   
    int main() 
    {   std::cout<<"First input: ";   
    std::cin.get();   
    std::cout<<"Clearing cin.\n"; 
      std::cin.clear();  
     ignore_line ( std::cin );  
     std::cout<<"All done.\n";
     } 
    

    This looks very promising because at first glance, it seems that you can attempt to push back a newline. If the operation fails, the last read character wasn't a newline and you're free to call ignore without blocking. If the operation succeeds, the newline is back and you can remove it with a single character ignore.

    Sadly, it doesn't work. putback isn't not required to do any of this predictably, which raises the question of why it's even available.

    But putback actually takes us close to a solution that seems plausible enough to work most of the time. Instead of relying on putback to fail or not, we can guarantee that the last read character is put back by using the sungetc member function of the stream's buffer. The trick is to unget the last character, then read it again and test it against a newline:

      #include 
        #include 
        #include 
        #include   
     template 
     void ignore_line ( std::basic_istream& in ) { 
      if ( in.rdbuf()->sungetc() != std::char_traits::eof()
            && in.get() != in.widen ( '\n' ) )   {
            in.ignore ( std::numeric_limits::max(), in.widen ( '\n' ) );  
     } 
    }   
    int main() {   
    std::cout<<"First input: ";  
     std::cin.get();   
    std::cout<<"Clearing cin.\n";   
    std::cin.clear();   
    ignore_line ( std::cin );   
    std::cout<<"All done.\n";
     }
    

    The reason we use sungetc instead of istream's unget is because unget returns the stream, but sungetc returns either the character that was pushed back, or EOF. This way we can tell if the function failed or not more easily.

    If sungetc fails, one of the following will be true:

    1) The stream is in an error state. 2) There are no characters to unget. 3) The stream doesn't support ungetting characters.

    If sungetc succeeds, there will always be a character to read and test against the newline. If that character matches a newline, then the last read character was also a newline and we don't need to call ignore. If the character doesn't match, then a full line hasn't been read and we can safely call ignore without blocking.

    If the stream is in an error state, that's something the calling code has to deal with. If there are no characters to unget, then that's precisely what this solution is designed to properly handle. But, if the stream doesn't support ungetting characters, that's an issue. The ignore_line function will always fail to discard characters, so for those implementations that don't support ungetting characters, we can add a flag that forces an ignore. It's sometimes useful to know how many characters were ignored as well, so let's add that too and we have the final solution:

       #include 
        #include 
        #include    
    template  
    std::streamsize ignore_line (   std::basic_istream& in, bool always_discard = false ) { 
      std::streamsize nread = 0;
            if ( always_discard || ( in.rdbuf()->sungetc() != std::char_traits::eof()
            && in.get() != in.widen ( '\n' ) ) )  
     {
            // The stream is good, and we haven't
            // read a full line yet, so clear it out
            in.ignore ( std::numeric_limits::max(), in.widen ( '\n' ) );
            nread = in.gcount();   }
            return nread; 
    }
    

    Just for good measure, I'll also include a manipulator that calls ignore_line and also a manipulator that uses ignore_line to pause the program. That way the unwashed masses can stop using system ( "PAUSE" ); and getch();:

      class ignoreline { 
      bool _always_discard;
       mutable std::streamsize _nread; 
    public:  
     ignoreline ( bool always_discard = false )
            : _always_discard ( always_discard ), _nread ( 0 )   {}
            std::streamsize gcount() const { return _nread;
     }
            template   
     friend std::basic_istream& operator>> (        std::basic_istream& in, const ignoreline& manip )  
     {
            manip._nread = ignore_line ( in, manip._always_discard );
            return in;  
     } 
    };  
     class pause { 
      ignoreline _ignore; 
    public:   
    pause ( bool always_discard = false )        : _ignore ( always_discard )   {}
            std::streamsize gcount() 
    const { return _ignore.gcount(); 
    }
            template  
      friend std::basic_istream& operator>> (        std::basic_istream& in, const pause& manip )   
    {
            if ( !( in>> manip._ignore ) )
              in.clear();
    
            std::cout<<"Press Enter to continue . . .";
    
            return in.ignore();  
     } 
    }; 
    

    Now all three cases behave identically:

         int main() 
    {   std::cout<<"First input: "; 
      std::cin.get();   
    std::cout<<"Clearing cin.\n";  
     std::cin>> ignoreline();  
     std::cout<<"All done.\n";  
     std::cin>> pause();
     } 
    

    And the moral of the story is: It's never as simple as it seems, writing portable code that does what you want is extremely difficult, and the iostream library is a huge mess.

    NOTE: If you are a beginner forget everything and just understand there is a flushing problem and use cin

提交回复
热议问题