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:
- Destroy leading whitespace in the input buffer.
- 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).
- 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
:
- Read characters from
cin
into the provided string variable until a newline character\n
is reached. - 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:
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:
- Note where the semicolons are. None of the curly braces are followed by semicolons, and the conditions inside the if statements don’t get semicolons!
- Else blocks are not mandatory. You can have a single if statement or an if statement followed by a few else-if’s without an else statement at the end.
- Curly braces are not mandatory. This will cause the if statement to run the first statement (i.e., up to the first semicolon) only. It is strongly recommended that you use curly braces all the time, but unfortunately programmers can be lazy and omit them. For instance, the output of the following code is
pie
; can you see why?
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:
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 double
s 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:
- And:
p && q
, bothp
andq
must be true. - Or:
p || q
, eitherp
orq
must be true.
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:
- Explain, in pseudocode, how the solution above works. Add comments to make the code readable.
- Give two examples of user inputs that cause the program to fail in two different ways.
- Modify the solution so that it works in all cases.