Hunter Liu's Website

6. Week 3 Thursday: If Statements

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

Let’s first begin by reviewing how if statements work. These actually refer to a family of three related statements: the if, else if, and else blocks.

1if( /* boolean condition */ ) {
2    // do something... 
3} else if( /* another boolean condition */ ) {
4    // do something else... 
5} else { 
6    // do a third thing... 
7} 

These boolean conditions are, as the name suggests, true or false statements, such as assessing if a string is longer than 5 characters or comparing two numbers (among many others). Having had refreshed this syntax,

Warm-up 1.

Write a program that prompts a user for a single character, then tells them if that character is a lowercase letter, an uppercase letter, an asterisk *, or something else.

SAMPLE RUNS: 

Please enter a character: a
You entered a lowercase letter.

Please enter a character: * 
You entered an asterisk. 

Please enter a character: 3
You entered something wack. 

Writing pseudocode that describes what the program should do is relatively straightforward.

  1. Prompt the user for a character and store the input in a char variable ch.
  2. If ch is between 'a' and 'z', tell the user it’s a lowercase letter.
  3. If instead ch is between 'A' and 'Z', tell the user it’s an uppercase letter.
  4. If instead ch is exactly '*', tell the user it’s an asterisk.
  5. In any other case, tell the user they entered something else.

Hopefully you are able to follow the logic presented here. Let us consider the following realisation of the program, though:

 1#include <iostream> 
 2using namespace std; 
 3
 4int main() {
 5    // Step 1
 6    cout << "Please enter a character: "; 
 7    char ch; 
 8    cin >> ch; 
 9
10    // Step 2 
11    if('a' <= ch <= 'z') {
12        cout << "You entered a lowercase letter." << endl; 
13    } 
14
15    // Step 3 
16    if('A' <= ch <= 'Z') {
17        cout << "You entered an uppercase letter." << endl; 
18    } 
19
20    // Step 4 
21    if(ch = '*') {
22        cout << "You entered an asterisk." << endl; 
23    } 
24
25    // Step 5 
26    else {
27        cout << "You entered something wack." << endl; 
28    } 
29
30    return 0; 
31}

While this is a reasonable transliteration of the pseudocode, it is riddled with errors. In fact, no matter what character I input, I get an output of

You entered a lowercase letter. 
You entered an uppercase letter. 
You entered something wack. 

Problem 2.

Identify all the logical errors present in the above code.

First and foremost, we need to fix the boolean conditions we’ve written. While these look mathematically reasonable, C++ doesn’t interpret 'a' <= ch <= 'z' correctly. What we meant to say was, “if 'a' <= ch and ch <= 'z'”, or if('a' <= ch && ch <= 'z'). Let’s fix that now:

 5// Step 1
 6cout << "Please enter a character: "; 
 7char ch; 
 8cin >> ch; 
 9
10// Step 2 
11if('a' <= ch && ch <= 'z') {
12    cout << "You entered a lowercase letter." << endl; 
13} 
14
15// Step 3 
16if('A' <= ch && ch <= 'Z') {
17    cout << "You entered an uppercase letter." << endl; 
18} 
19
20// Step 4 
21if(ch = '*') {
22    cout << "You entered an asterisk." << endl; 
23} 
24
25// Step 5 
26else {
27    cout << "You entered something wack." << endl; 
28} 

Remark 3.

So what does 'a' <= ch <= 'z' actually do then? C++ first evaluates 'a' <= ch as true or false, then compares true <= 'z' or false <= 'z', whichever it came out to. It can’t compare a true or false value to a character (number), so it converts the true/false value into a number, typically 0 or 1, then checks. This almost always results in true as the result… do not make this mistake! Ever!

Now if we run the code, it’ll correctly identify the lowercase/uppercase letters, but no matter what I enter it still prints out You entered an asterisk. at the end. This suggests something is wrong with the conditional again, and indeed ch = '*' does not check if ch is an asterisk. Instead, it should be ch == '*'.

 5// Step 1
 6cout << "Please enter a character: "; 
 7char ch; 
 8cin >> ch; 
 9
10// Step 2 
11if('a' <= ch && ch <= 'z') {
12    cout << "You entered a lowercase letter." << endl; 
13} 
14
15// Step 3 
16if('A' <= ch && ch <= 'Z') {
17    cout << "You entered an uppercase letter." << endl; 
18} 
19
20// Step 4 
21if(ch == '*') {
22    cout << "You entered an asterisk." << endl; 
23} 
24
25// Step 5 
26else {
27    cout << "You entered something wack." << endl; 
28} 

Remark 4.

This begs again the question, what is C++ thinking when it sees if(ch = '*')? It in fact interprets this as “set ch equal to '*', then convert that '*' into a true or a false”. Nonzero numbers are always interpreted as “true”, hence we kept seeing the output “You entered an asterisk.”

The point: use == instead of = to check for equality!

We are almost done, but something still isn’t quite right: whenever I enter something that’s not an asterisk, I see You entered something wack., regardless of if my input was a letter or not. This is because the if statements on lines 11 and 16 are unrelated to the if-else pair on lines 21 and 26! When C++ gets to line 21, it thinks, “If ch is equal to '*', say it’s an asterisk. If ch is not equal to '*', say it’s something wack.” We need to keep all four branches in one big if-else if-else if-else group!

 5// Step 1
 6cout << "Please enter a character: "; 
 7char ch; 
 8cin >> ch; 
 9
10// Step 2 
11if('a' <= ch && ch <= 'z') {
12    cout << "You entered a lowercase letter." << endl; 
13} 
14
15// Step 3 
16else if('A' <= ch && ch <= 'Z') {
17    cout << "You entered an uppercase letter." << endl; 
18} 
19
20// Step 4 
21else if(ch == '*') {
22    cout << "You entered an asterisk." << endl; 
23} 
24
25// Step 5 
26else {
27    cout << "You entered something wack." << endl; 
28} 

Let’s move on to a more substantial problem:

Problem 5. Evenly Spaced

Write a program that asks the user for three integers, then prints “Evenly spaced” if the three numbers are evenly spaced, and “Unevenly spaced” otherwise. For instance, the numbers 2, 4, and 6 are evenly spaced, regardless of which order they are input, but 7, 10, and 14 are not.

Let’s analyse this particular problem together. A naïve but entirely plausible solution in pseudocode may look as follows:

  1. Let the user enter three integers, \(a\), \(b\), and \(c\).
  2. If \(b-a=c-b\), the numbers are evenly spaced.
  3. Otherwise, the numbers are not evenly spaced.

This works perfectly well when the numbers are in increasing or decreasing order — we’re checking if the differences between consecutive numbers are the same.

However, this algorithm falls apart entirely when the three numbers are out of order: the user inputting 4 2 6 would cause a = 4, b = 2, c = 6 to be read in, and b - a = -2 while c - b = 4.

One immediate way to remedy this is to first put the numbers in order, then perform the above algorithm. In pseudocode,

  1. Let the user enter three integers, \(a\), \(b\), and \(c\).
  2. By swapping the contents of \(a\), \(b\), and/or \(c\) as necessary, put the three numbers in ascending or descending order.
  3. If \(b-a=c-b\), the numbers are evenly spaced.
  4. Otherwise, they are not evenly spaced.

Try to implement these steps algorithmically before peeking at the solution!

Solution

This is not the only way to solve this problem; there are may different ways to place numbers in increasing or decreasing order. We will handle this by breaking up all the possible scenarios into distinct cases.

Suppose the user has entered the integers \(a, b, c\); our job is to place them in either ascending or descending order.

  1. If \(a \leq b \leq c\), we have nothing to do.
  2. If \(c \leq b \leq a\), we also have nothing to do.
  3. If \(a \leq c < b\), we should swap \(b\) and \(c\).
  4. If \(c \leq a < b\), we should swap \(a\) and \(b\).
  5. If \(b < a \leq c\), we should swap \(a\) and \(b\).
  6. If \(b < c \leq a\), we should swap \(b\) and \(c\).

The above analysis represents every possible ordering fof the three inputs; we are describing how to handle each case.

We could write four if/else-if branches for scenarios 3-6, but this would result in some redundancies: the actions we need to perform for cases 4 and 5 are the same. Thus, we may write this in another way, grouping by the actions we are performing rather than the scenario:

  • We should swap \(a\) and \(b\) when…
    • \(b < a \leq c\) (case 5)
    • \(b > a \geq c\) (case 4)
  • We should swap \(b\) and \(c\) when…
    • \(b < c \leq a\) (case 6)
    • \(b > c \geq a\) (case 3)

Converting this into actual code yields:

 1#include <iostream> 
 2
 3using namespace std; 
 4
 5int main() {
 6    // 1. read user input into 3 integers, a, b, and c 
 7    int a, b, c; 
 8    cout << "Please enter 3 integers: "; 
 9    cin >> a >> b >> c; 
10
11    // 2. rearrange the three integers so they're "in order". 
12    // cases 4 and 5, swapping a and b
13    if((b < a && a >= c) || (b > a && a >= c)) { 
14        int temp = b; 
15        b = a; 
16        a = temp; 
17    } 
18
19    // cases 3 and 6, swapping b and c
20    else if((b < c && c <= a) || (b > c && c >= a)) {
21        int temp = b; 
22        b = c; 
23        c = temp; 
24    } 
25
26    // 3. if b - a and c - b are equal, we are evenly spaced 
27    if( b - a == c - b ) {
28        cout << "Evenly spaced" << endl; 
29    } 
30
31    // 4. otherwise, they aren't evenly spaced. 
32    else {
33        cout << "Unevenly spaced" << endl; 
34    } 
35
36    return 0; 
37} 
Challenge
There is a solution to this problem that does not involve rearranging the user’s three inputs. Can you think of a way to do this?