This was written before I saw seeplus's reply above.
SubZeroWins wrote: |
---|
I ran into an issue using a getline() followed by a cin, where it would skip the getline() and act weird. I researched that you can add cin.ignore() to fix this, but never knew why this is a problem in the first place? " |
This is a common problem when
<< is followed by
getline.
It is important to understand that streams are just sequences of characters that is written to at one end and read from at the other end (like a queue).
When you type something and press enter it will add the characters that you have typed + a newline character
'\n' to the end of the standard input stream (std::cin). If the stream is empty when you try to read from it it will wait until more input has been supplied.
When you use
<< to read from a stream it will first discard any leading whitespace characters (e.g. spaces, tabs, newlines) and then it will start reading the value of whatever data type that it is that you're trying to read.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
int main()
{
std::istringstream stream(" Hello 1.5 2.7");
// stream buffer content: " Hello 1.5 2.7"
std::string str;
stream >> str;
std::cout << "'" << str << "'\n"; // prints 'Hello'
// stream buffer content: " 1.5 2.7"
double dval;
stream >> dval;
std::cout << dval << "\n"; // prints 1.5
// stream buffer content: " 2.7"
int ival;
stream >> ival;
std::cout << ival << "\n"; // prints 2
// stream buffer content: ".7"
}
|
When you use
getline to read from a stream it simply reads all characters until it finds a newline character (the newline character is discarded).
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
int main()
{
std::istringstream stream("Test\n x y 67 Test whatever \n");
// stream buffer content: "Test\n x y 67 Test whatever \n"
std::string line1;
std::getline(stream, line1);
std::cout << "'" << line1 << "'\n"; // prints 'Test'
// stream buffer content: " x y 67 Test whatever \n"
std::string line2;
std::getline(stream, line2);
std::cout << "'" << line2 << "'\n"; // prints ' x y 67 Test whatever '
// stream buffer content: ""
}
|
What happens when you use >> followed by getline is that the getline will continue reading the rest of the line where the >> stopped reading.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
int main()
{
std::istringstream stream("A B\nApe\nBird\n");
// stream buffer content: "A B\nApe\nBird\n"
std::string a;
stream >> a;
std::cout << "'" << a << "'\n"; // prints 'A'
// stream buffer content: " B\nApe\nBird\n"
std::string b;
std::getline(stream, b);
std::cout << "'" << b << "'\n"; // prints ' B'
// stream buffer content: "Ape\nBird\n"
std::string c;
stream >> c;
std::cout << "'" << c << "'\n"; // prints 'Ape'
// stream buffer content: "\nBird\n"
std::string d;
std::getline(stream, d);
std::cout << "'" << d << "'\n"; // prints ''
// stream buffer content: "Bird\n"
}
|
There are at least three different strategies to deal with this problem.
1.
Always discard the rest of the line after using >>
Code that reads input from the user is responsible for reading the whole line. If you use >> to read the input then you can indeed use ignore() to discard the newline character.
1 2 3
|
int val;
std::cin >> val;
std::cin.ignore();
|
But this only works if there wasn't any additional characters between the value and the newline character (maybe the user pressed space before pressing enter).
To avoid this problem you can use the following code instead:
|
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
This will discard the rest of the line no matter how long it is. (The first argument is the maximum number of characters to discard. The second argument specifies the character where it should stop.)
Note that you place this
after you have used
>>, not before using
getline because at that point you might not always know whether you need to discard anything.
If you want to validate the remaining characters (e.g. to see that they are all whitespace characters) you might want to use
getline instead of
ignore so that you can inspect them as a string.
2.
Always use std::ws before using getline
A simpler strategy is to simply discard any whitespace characters before using std::getline. This usually works fine as long as the left-over characters from the previous line are all whitespace characters.
1 2 3
|
std::string line;
std::cin >> std::ws;
std::getline(std::cin, line);
|
You can even combine
getline and
ws into one line which can be convenient.
1 2
|
std::string line;
std::getline(std::cin >> std::ws, line);
|
This makes it behave much more like the
>> operator. If the user press enter without entering anything it will continue to wait just like when using >>.
A downside is that you cannot read empty lines and any whitespace characters at the beginning of the line will be lost which is something that you might
sometimes care about.
3.
Always use getline
Just use std::getline to read each line from the user and if you need to extract values from the lines you can use std::istringstream.
1 2 3 4 5 6 7
|
std::cout << "Enter two integers: ";
std::string line;
std::getline(std::cin, line);
std::istringstream line_stream(line);
int n1, n2;
line_stream >> n1 >> n2;
std::cout << "You have entered " << n1 << " and " << n2 << ".\n";
|
This is essentially just a variation of strategy 1. It requires a bit more code but prevents all issues with lines interfering with each other.