5. Week 3 Tuesday: The Input Buffer
≪ 4. Week 2 Thursday: Increments, Decrements, and Strings | Table of Contents | 6. Week 3 Thursday: Review and Practise ≫As you have explored in class, there are some situations when using cin
to directly insert user input into a variable is appropriate, and other situations where getline
is necessary to capture a user’s input which may contain spaces. However, this behaviour can be somewhat unpredictable when cin
and getline
are used together.
Consider the following program, which asks a user for their and their parents’ full names, then prints out a short blurb using those names.
1#include <iostream> // needed for cin and cout
2#include <string> // needed for strings and getline
3
4using namespace std;
5
6int main() {
7 cout << "What is your full name? ";
8 string name;
9 getline(cin, name);
10
11 cout << "What is your mother's full name? ";
12 string mom_first, mom_last;
13 cin >> mom_first;
14 cin >> mom_list;
15
16 cout << "What is your father's full name? ";
17 string father;
18 getline(cin, father);
19
20 cout << name << ", child of Mrs. "
21 << mom_first << " " << mom_last << " and Mr. "
22 << father << "." << endl;
23
24 return 0;
25}
Running this program produces somewhat surprising behaviour. After entering the user’s name and the user’s mother’s name, the program doesn’t even wait for the user’s father’s name. In the final blurb, the father’s name is left empty. In short, this occurs because while both cin
and getline
are sensitive to linebreaks (i.e., when the user presses Enter
), they respond to these events slightly differently in ways that impact future cin
and getline
behaviour.
Such issues (almost) never occur when using cin
on its own without getline
. You may be tempted to adamantly refuse to use one or the other, and while there are alternatives, that’s a dark place you probably never want to go to.
What is an input buffer?
You should think of your program as being one “node” on a digitised assembly line, with a bunch of conveyor belts supplying information to and from your program. These conveyor belts may lead to files on your computer or even other programs. They behave like the conveyor belts at checkout lines in supermarkets and such: data can be loaded onto the belts freely, but the belt only moves as quickly as the program on the receiving end can process them.
cin
and cout
represent two special conveyor belts in your computer. Whenever your program prints something to cout
, that information is loaded onto a belt that leads to your screen, where that data is printed out one character at a time. The cin
conveyor belt flows the other way: this time, the user loads the belt with text input when the program is running, and the program waits for information to arrive on the belt for processing. This belt is called the “input buffer”.
Remark 1. Input and Output Streams
What I am calling “conveyor belts” are actually known as “streams” in C++. Streams that provide information to the program, such as the input buffer, are called “input streams” and have the type istream
. Streams that lead data out of the program are called “output streams” and have the type ostream
.
We will only work with cin
and cout
in this class, but during
Reading the input buffer
When cin
needs to put something into a variable, it performs the following steps:
- Discard any spaces at the front of the input buffer.
- Read as many “valid characters” as possible. Whitespace will always be considered an invalid character.
- For integers, “valid characters” are just digits, and possibly a
+
or-
at the front. - For doubles, “valid characters” are digits, a single decimal point, a leading
+
or-
, and evene
in scientific notation (like-1.47e16
). - For characters, “valid characters” constitutes a single nonspace character.
- For strings, “valid characters” is a contiguous chunk of any nonspace characters.
- For integers, “valid characters” are just digits, and possibly a
- When an invalid character is seen on the belt, leave it on the belt and stop.
On the other hand, getline
performs the following steps:
- Read characters from the input buffer (including leading spaces!) and store them in a string until a newline character
\n
is found. - When a newline character
\n
is encountered on the belt, remove it from the belt and stop.
Let’s analyse how the program from the beginning runs. Suppose I want to tell the program that my name is “George Salmon”, that my mother’s name is “Anna Mayfly”, and that my father’s name is “John Salmon”. After entering my name, the input buffer contains:
George Fish\n
The \n
represents the “Enter” key. After line 9
is run, name
contains George Fish
, and the \n
is discarded. Thus the input buffer is empty after line 9
.
After being prompted for my mother’s name on line 11
, the input buffer will contain
Anna Mayfly\n
However, this time the program uses cin
to read the name. After line 13
, mom_first
contains the string Anna
, and the input buffer is
Mayfly\n
^
Note the leading space. After line 14
, the leading space is ignored by cin
, and Mayfly
is read into the mom_last
variable. The input buffer is now
\n
Finally, after being prompted for my father’s name, the input buffer contains
\nJohn Fish\n
However, the getline
on line 18 reaches a \n
immediately! It thus stores an empty string into father
, discards the leading \n
, and calls it a day. Thus, the input buffer becomes
John Fish\n
My father’s name is stuck in the input buffer, and the program produces faulty output at the end.
If only we could get rid of the leading \n
before using getline
!
The fix to all of this is to use cin.ignore()
. This function says to discard the next character on the cin
conveyor belt. Thus, to fix our program, we need only add cin.ignore()
after line 14, which destroys the extra \n
left behind by its predecessors.
Fixed Code
1#include <iostream> // needed for cin and cout
2#include <string> // needed for strings and getline
3
4using namespace std;
5
6int main() {
7 cout << "What is your full name? ";
8 string name;
9 getline(cin, name);
10
11 cout << "What is your mother's full name? ";
12 string mom_first, mom_last;
13 cin >> mom_first;
14 cin >> mom_last;
15 cin.ignore();
16
17 cout << "What is your father's full name? ";
18 string father;
19 getline(cin, father);
20
21 cout << name << ", child of Mrs. "
22 << mom_first << " " << mom_last << " and Mr. "
23 << father << "." << endl;
24
25 return 0;
26}
Practise 2.
I adapted the following problem from Prof. Michael Andrews’ most recent PIC 10A midterm.
Consider the following code:
Code
int i1, i2, i3, i4, i5;
char c;
string s;
cin >> i1;
cin >> i2;
getline(cin, s);
cin >> i3;
cin.ignore();
cin >> c;
cin >> i4 >> i5;
cout << endl;
cout << "Line 1: " << i1 << endl;
cout << "Line 2: " << i2 << endl; // These variables
cout << "Line 3: " << s << endl; // are printed in
cout << "Line 4: " << i3 << endl; // the same order
cout << "Line 5: " << c << endl; // that they are
cout << "Line 6: " << i4 << endl; // assigned to.
cout << "Line 7: " << i5 << endl;
Suppose you entered the following four lines of input, with spaces indicated:
\n
9 8\n
^
7 6543\n
^
2 1012 345 678 911\n
^ ^ ^ ^
Predict the output of the code, and determine the contents of the input buffer at the end of each line of code that processes user input.
Reformatting input
There are times when one must decide on processing input using cin
or getline
, and both may present their own upsides and downsides. For instance:
- Suppose you ask for a user’s phone number and need to extract the digits only from an input such as
+1 (657) 114-5721
. - Suppose you are processing a table of numbers, with each row getting its own line and entries separated by commas. For instance, one row may appear as
334,447.3,-14.2,599.0,184.3
.
We are not particularly well-equipped to deal with either example at the moment — we will need to learn about loops and vectors first (stick around for a few weeks and find out!). The point is that getline
will give you access to an entire line of user input at a time, but you would have to do some additional work to extract numerical data from the input. Using cin
for the second example may be more suitable, but the first example can be processed as a string (especially since the numerical values in the input aren’t very relevant).
To highlight how each method of processing input works, let’s work through the following example:
Example 3. Distance to Home
Write a program that allows a user to input an ordered pair of numbers \(\left( x, y \right)\) and computes its distance to the origin.
SAMPLE RUNS:
Please input an ordered pair (x, y): (3, 4)
The point (3, 4) is a distance of 5.0 from the origin.
Please input an ordered pair (x, y): (4, 7)
The point (4, 7) is a distance of 8.06226 from the origin.
Implement two solutions: one using only cin
to obtain user input, the other using only getline
.
For the getline
solutions, the functions stod, rfind, and substr may be helpful. Make sure you can read through the reference and figure out how these functions work, even if you can’t understand all the syntax! I think it’s best to see the examples provided on the reference pages.
Try doing the problem yourself before peeking at the solutions, though I suppose I can’t stop you. Note that the user will be inputting the ordered pair with the parentheses and the comma surrounding it.
Hint for cin implementation
cin >> ch;
, where ch
is a char
variable. Why does this work?Pseudocode with cin
Using the hint above, we may construct the following pseudocode:
- Prompt the user for input.
- Read a single character from the input stream. This should be the
(
in the ordered pair. - Read a double called
x
from the input stream. - Read a single character from the input stream. This should be the
,
in the ordered pair. - Read a double called
y
from the input stream. - Compute the distance \(d=\sqrt{x^2+y^2}\) from the origin.
- Print the results to the screen.
Most notably, we left the closing parentheses in the input stream, as we aren’t reading any more input after we obtain y
. In other applications where we may expect more output to follow, this would be a very bad idea, and it would be important to clear that out as well.
Implementation with cin
1#include <iostream> // needed for cin and cout
2#include <cmath> // needed for sqrt
3
4using namespace std;
5
6int main() {
7 // step 1
8 cout << "Please input an ordered pair (x, y): ";
9
10 // steps 2-5, compressed into a shorter line!
11 char ch;
12 double x, y;
13 cin >> ch >> x >> ch >> y;
14
15 // step 6
16 double d = sqrt(x * x + y * y);
17
18 // step 7 - record output
19 cout << "The point (" << x << ", " << y
20 << ") is a distance of " << d
21 << " from the origin." << endl;
22
23 return 0;
24}
Pseudocode with getline
For this implementation, we need to determine which indices the parentheses and comma lie in. Then, we’ll extract the substrings between these three extra characters before converting them to doubles.
- Prompt the user for input.
- Store the user’s full input in a string called
line
. - Create three variables,
paren1, comma, paren2
, which represent the indices of the opening parentheses, comma, and closing parentheses, respectively. - Extract the substring between
paren1
andcomma
(exclusive on both sides) and convert it to a double calledx
. - Extract the substring between
comma
andparen2
(exclusive on both sides) and convert it to a double calledy
. - Compute the distance \(d=\sqrt{x^2+y^2}\) from the origin.
- Print the results to the screen.
You will need to use rfind
on step 3, and substr
and stod
on steps 4 and 5. Make sure you use size_t
variables to store the indices in step 3!
Implementation with getline
1#include <string> // needed for getline, rfind, stod
2#include <cmath> // needed for sqrt
3
4using namespace std;
5
6int main() {
7 // steps 1 and 2
8 cout << "Please enter an ordered pair (x, y): ";
9 string line;
10 getline(cin, line);
11
12 // step 3
13 size_t paren1 = line.rfind("(");
14 size_t comma = line.rfind(",");
15 size_t paren2 = line.rfind(")");
16
17 // steps 4 and 5
18 // remember that substr is inclusive in the first index,
19 // hence the +1. Make sure you know how I'm counting the
20 // length of these substrings!
21 string x_string = line.substr(paren1 + 1, comma - paren1 - 1);
22 double x = stod(x_string);
23
24 string y_string = line.substr(comma + 1, paren2 - paren1 - 1);
25 double y = stod(y_string);
26
27 // step 6
28 double d = sqrt(x * x + y * y);
29
30 // step 7 - record output
31 cout << "The point (" << x << ", " << y
32 << ") is a distance of " << d
33 << " from the origin." << endl;
34
35 return 0;
36}