Hunter Liu's Website

6. Week 3 Thursday: getline, If Statements, and Conditionals

≪ 5. Week 3 Tuesday: Handling Input with Strings and cin | Table of Contents | 7. Week 4 Tuesday: Practise with Loops! ≫

Last Tuesday, we saw some examples of how cin could behave somewhat unexpectedly when taking inputs. But it’s consistent, and we outlined the three steps that it takes whenever it accepts input:

  1. Destroy leading whitespace in the input buffer.
  2. Read as many characters from the input buffer as possible, stopping either at whitespace or when the input no longer makes sense (e.g. in a number).
  3. Rinse and repeat.

However, as step 2 indicates, cin stops at the end of every word, and there are scenarios when you don’t know exactly how many words will be in someone’s input. For instance, people’s names can contain a variable number of middle names, and there’s no good way around this with cin.

To accept a full line of input, spaces and everything, we can use the getline function. You need a string variable to use getline, and we’ll demonstrate how it works in the following program:

 1#include <iostream> // needed for cin, cout 
 2#include <string>   // needed for strings, getline 
 3
 4using namespace std; 
 5
 6int main() {
 7    cout << "What is your full name?" << endl; 
 8    string user_name; 
 9
10    // reads a full line of input into the string user_name 
11    getline(cin, user_name); 
12
13    cout << "What is your father's first name?" << endl; 
14    string father; cin >> father; 
15
16    cout << "Hello " << user_name 
17         << ", esteemed son of " << father << "!" << endl; 
18
19    return 0;
20} 

It doesn’t matter how many middle names you have, this will always work.

However, something fishy happens when we reverse the order that these questions are asked:

 1#include <iostream> // needed for cin, cout 
 2#include <string>   // needed for strings, getline 
 3
 4using namespace std; 
 5
 6int main() {
 7    cout << "What is your father's first name?" << endl; 
 8    string father; cin >> father; 
 9
10    cout << "What is your full name?" << endl; 
11    string user_name; 
12
13    // reads a full line of input into the string user_name 
14    getline(cin, user_name); 
15
16    cout << "Hello " << user_name 
17         << ", esteemed son of " << father << "!" << endl; 
18
19    return 0;
20} 

First of all, if you enter more than one name for the father’s first name, this of course doesn’t work as anticipated. But more interestingly, even if you just enter one name for the father, the user’s name is left blank and the getline appears to be skipped!

This is because getline uses different rules than cin:

  1. Read characters from cin into the provided string variable until a newline character \n is reached.
  2. Delete, but do not store, the newline character.

The cin command on line 8 reads the father’s first name from the input buffer and stops when it sees the newline character. However, it leaves it in the input buffer, and this same newline character is seen by the getline function a few lines later! This results in nothing being read into the user_name variable.

Because of these conflicting behaviours, it’s generally a good idea to stick to only using cin or only getline. However, sometimes the benefits of using cin are too appealing to resist. To fix this discrepancy, one can use the cin.ignore function. This needs two pieces of data: a number and a character. cin.ignore([ n ]) will delete the first n characters in the input buffer unconditionally. cin.ignore([ n ], [ ch ]) will ignore up to n characters, but stops if it sees the character ch and destroys it too. For instance, cin.ignore(10000, '\n'); will delete up to 10,000 characters, stopping if it sees a newline character. We can add this line after the cin command and before the getline command to fix our program:

 1#include <iostream> // needed for cin, cout 
 2#include <string>   // needed for strings, getline 
 3
 4using namespace std; 
 5
 6int main() {
 7    cout << "What is your father's first name?" << endl; 
 8    string father; cin >> father; 
 9
10    // skip the rest of the line! 
11    cin.ignore(10000, '\n'); 
12
13    cout << "What is your full name?" << endl; 
14    string user_name; 
15
16    // reads a full line of input into the string user_name 
17    getline(cin, user_name); 
18
19    cout << "Hello " << user_name 
20         << ", esteemed son of " << father << "!" << endl; 
21
22    return 0;
23} 

Remark 1.

You may be wondering, “What if the user entered more than 10,000 characters before hitting enter?” Generally, there’s always a way for a pathalogical user input to ruin your life. However, for the ignore function, there is a special constant defined by the limits library (i.e., you have to #include <limits>) that makes cin ignore as many characters as it needs to before reaching the desired character. You can find more details on the C++ documentation.

Problem 2.

Consider the following C++ program:

Code
 1#include <iostream> 
 2#include <string> 
 3
 4using namespace std; 
 5
 6int main() {
 7    int i1, i2, i3; 
 8    double d; 
 9    char ch1, ch2; 
10    string s1, s2; 
11
12    cin >> i1 >> ch1 >> d; 
13    cin >> s1; 
14    getline(cin, s2); 
15    cin.ignore(10000, '\n'); 
16    ch2 = cin.peek(); 
17    cin >> i2 >> i3; 
18
19    // prints the variables in the 
20    // order they were declared. 
21    cout << i1 << endl; 
22    cout << i2 << endl; 
23    cout << i3 << endl; 
24    cout << d << endl; 
25    cout << ch1 << endl; 
26    cout << ch2 << endl; 
27    cout << s1 << endl; 
28    cout << s2 << endl; 
29
30    return 0; 
31} 

Suppose the user enters the following inputs, with newline characters explicitly indicated:

11.12.13.14\n
string number 2\n
31 883\n
94 22\n

Determine the output of the program.

If Statements and Conditionals

If statements are our first example of control flow, and their syntax is as follows:

1if( [statement] ) {
2    // code... 
3} 

The statement, called a conditional, must be a boolean expression. The code within the curly braces only runs if the conditional is true. You can also chain if statements with an else if or an else block:

 1int x = 73;
 2if(x >= 90) {
 3    cout << "You got an A" << endl;
 4} else if(x >= 80) {
 5    cout << "You got a B" << endl;
 6} else if(x >= 70) {
 7    cout << "You got a C" << endl;
 8} else if(x >= 60) {
 9    cout << "You got a D" << endl;
10} else {
11    cout << "See me after class." << endl;
12}

This snippet of code will output You got a C. It will check the condition x >= 90, which is false. Then, it checks x >= 80, which is also false. It finally checks x >= 70, which is true, and so it runs the following code block. It then skips the rest of the else if/else blocks!

There are several things to remark:

1int x = 50;
2if(x < 0)
3    cout << "I love ";
4    cout << "pie" << endl;

Let’s look a bit closer at these boolean conditions. Most often, you’ll be performing a comparison, and we’ve already seen these before: <, >, <=, and >= all do exactly what you think they do. They can only be used to compare two numeric quantities — integers, doubles, characters, etc. You can also compare if two quantities are equal using two equal signs:

 1int x = 37; 
 2cout << "Guess my secret number: " << endl; 
 3int input; cin >> input; 
 4
 5if(input == x) { 
 6    cout << "You guessed it!" << endl; 
 7} else {
 8    cout << "Haha I'm smarter than you " 
 9         << "*spits in your face*" << endl; 
10} 

Common Mistake 3.

When comparing two values, you have to use two equal signs ==. If you use one equal sign =, the code will still compile, but it won’t perform the comparison and may produce unexpected behaviour.

To check if two quantities are not equal, use !=. As a demonstration, consider the following code:

1cout << boolalpha; 
2cout << (20.15 * 100 != 2015) << endl; 

This will print true if 20.15 * 100 is not 2015, false otherwise. Against all common knowledge, this prints false. This is because of the limited precision of doubles in C++! You should in general stay away from using == with doubles.

Sometimes, however, we may have more than one condition that we want to be true, or several conditions of which we want at least one to be true. If p and q are two boolean expressions (i.e., true or false, such as the comparisons above), you can combine them in the following two ways:

Of course, one can string together any number of conditions using a mix of && and ||; however, you should be careful with the order in which they are combined together. This StackOverflow answer provides a great explanation; in short, && is evaluated first in absence of parentheses.

In addition to this “order of operations” business, C++ engages with something called lazy evaluation. Consider the following two somewhat contrived programs:

Program 1
 1#include <iostream> 
 2#include <string> 
 3
 4using namespace std; 
 5
 6int main() {
 7    cout << "Please enter a word: " << endl; 
 8    string word; cin >> word; 
 9
10    if(word.length() >= 5 && word.at(4) == 'a') {
11        cout << "You entered a very special word." << endl; 
12    } else {
13        cout << "Your word is kind of lame and stinky." << endl; 
14    } 
15
16    return 0; 
17} 
Program 2
 1#include <iostream> 
 2#include <string> 
 3
 4using namespace std; 
 5
 6int main() {
 7    cout << "Please enter a word: " << endl; 
 8    string word; cin >> word; 
 9
10    if(word.at(4) == 'a' && word.length() >= 5) { 
11        cout << "You entered a very special word." << endl; 
12    } else {
13        cout << "Your word is kind of lame and stinky." << endl; 
14    } 
15
16    return 0; 
17} 

When providing the input hi, the first program runs perfectly well while the second program crashes. This is because C++ evaluates the two conditions in order. In the first program, it checks if the word.length() >= 5 first. Seeing that it’s false, it has no reason to evaluate the second condition — whether it’s true or not has no impact on the value of the conditional — hence it skips checking word.at(4) and doesn’t crash the program. However, in the second program, it does things in the reverse order and tries to access a nonexistent index.

Problem 4. Quadratic Formula

Write a C++ program that asks the user for three real numbers \(a, b, c\), then prints out the real solutions to the equation \(ax^2+bx+c=0\). Recall that the quadratic formula is \(x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}\).

Sample runs:
INPUT    1 -1 -1         (x^2 - x - 1 = 0)
OUTPUT   Solutions: 1.61803, -0.618034

INPUT    1 2 1           (x^2 + 2x + 1 = 0)
OUTPUT   Solution: -1

Student Solution:

 1#include <iostream>
 2#include <cmath>
 3
 4using namespace std;
 5
 6int main() {
 7    double a, b, c, d;
 8    cin >> a >> b >> c;
 9    d = sqrt(b * b - 4 * a * c);
10
11    cout << "Solutions: "
12         << (-1 * b + d) / (2 * a)
13         << ", " << (-1 * b - d) / (2 * a) << endl;
14
15    return 0;
16}

Your job is threefold:

  1. Explain, in pseudocode, how the solution above works. Add comments to make the code readable.
  2. Give two examples of user inputs that cause the program to fail in two different ways.
  3. Modify the solution so that it works in all cases.