Hunter Liu's Website

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:

  1. Discard any spaces at the front of the input buffer.
  2. 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 even e 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.
  3. 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:

  1. Read characters from the input buffer (including leading spaces!) and store them in a string until a newline character \n is found.
  2. 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:

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
To discard a single non-space character from the input stream, you can use 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:

  1. Prompt the user for input.
  2. Read a single character from the input stream. This should be the ( in the ordered pair.
  3. Read a double called x from the input stream.
  4. Read a single character from the input stream. This should be the , in the ordered pair.
  5. Read a double called y from the input stream.
  6. Compute the distance \(d=\sqrt{x^2+y^2}\) from the origin.
  7. 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.

  1. Prompt the user for input.
  2. Store the user’s full input in a string called line.
  3. Create three variables, paren1, comma, paren2, which represent the indices of the opening parentheses, comma, and closing parentheses, respectively.
  4. Extract the substring between paren1 and comma (exclusive on both sides) and convert it to a double called x.
  5. Extract the substring between comma and paren2 (exclusive on both sides) and convert it to a double called y.
  6. Compute the distance \(d=\sqrt{x^2+y^2}\) from the origin.
  7. 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}