Review of IO and strings

Strings: Work through the middle/first/last name example.

String literals and escapes:

Characters:

char x = 'A';

The Conditional Operator

The conditional operator lets us choose between two expressions to evaluate, depending on a Boolean condition. For example, in our BMI program, we wanted to print a different message if the BMI was greater than 30 (indicating that the user is overweight) We can do this via

int bmi = ...
cout << bmi > 30 ?
        "You are overweight!" :
        "Doing good.";
cout << endl;

The conditional operator has the structure

boolean_condition ? true_expression : false_expression

First, boolean-condition is evaluated. If it evaluates to true, then true-expression is evaluated and becomes the value of the whole structure. If the condition evaluates to false, then false-expression is evaluated and becomes the value. Note that one and only one of true/false-expression is ever evaluated. (I.e., the conditional operator does short-circuiting, just like && and ||.) This means you can do things like this:

x != 0 ? 1 / x : 0

The true-side would be undefined if x was 0, but if x is 0 it will not be evaluated.

The ternary operator has lower precedence than all binary operators, except for the assignment operators.

It’s possible to “chain” ?: together to express an if-else-if-else structure:

if_condition1   ? true-branch :
else_condition2 ? else_true_branch : else_branch

For example, we could check against too-high and too-low BMI with

cout << bmi < 18 ? "You are underweight" :
        bmi > 30 ? "You are overweight" :
        "You are fine";

It’s also possible to write this:

a ? b ? c : d : e

But that’s confusing. Better to write it as

a ? (b ? c : d) : e

or even better yet, think about when c, d, and e are going to be produced:

a && b  ? c :
a && !b ? d :
          e

One interesting trick to note is that in the case of something like

condition ? a : b

where a and b are variables, the conditional operator will evaluate to the box named by a or b, rather than the value in it. (In general, C++ saves pulling the value out of a box until the very last moment, to give you opportunities to use it as a box, rather than as a simple value.) This means you can do something like this:

(which == 1 ? a : b) = 12;

Which variable will assigned to depends on whether the condition works out to true or false (and, because of short-circuiting, the other won’t be touched).

Note, also, that negating the condition is exactly equivalent to swapping the true and false expressions:

a  ? b : c    // is equivalent to
!a ? c : b

If you find yourself writing a negated condition, you might ask whether it would be clearer if you removed the negation, and just swapped the expressions.

Some examples:

a < b ? a : b // gives the larger of a and b
width == height ? "square" : "rectangle"

It’s important to note the difference between the conditional operator and the if-else statement. You should use the conditional operator if all you need to do is choose between two values, while if-else allows you to choose between two “courses of action”, two different sequences of statements.

Note that these two things are equivalent:

if(x < y)
    y = 1;
else
    y = 2;

and

y = x < y ? 1 : 2;

The second is more concise, but you might find the first easier to understand.

Conditional statements: branching

We continue our adventure into mixing up the flow of control (from the straight line top-to-bottom process it used before). We’re going to add branching, the ability for our code to take different paths, depending on the true/false value of some condition

The if-else control structure

An if-else structure allows our code to go one of two ways, depending on whether a condition is true or false. While the conditional operator allowed us to switch between values, the if-else control structure allows us to switch between statements (or blocks, of course). The most basic if-else looks like this:

if(condition)
    body;

If the condition evaluates to true, then the body (a single statement, or block) is executed next. Otherwise, execution continues with the first statement immediately after the body. For example:

int i;
cin >> i
if(i < 0)
    cout << "(negative) ";
cout << "i = " << i << endl;

Depending on the sign of the user’s input, this will either print

(negative) i = ...

or

i = ...

This kind of if structure is useful when you want something to happen only if a condition is true, but you don’t want anything in particular to happen when it is false. (Note how, above, the next line after the if is printed in both the true and false cases.)

If you want to do something specifically both when the condition is true, and when it is false, you can add an else clause:

if(condition)
    true-body;
else
    false-body;

If the condition evaluates to true, then the true-body will be executed, otherwise the false-body will be executed. After one of the two bodies has been executed, the flow of control continues with the next statement immediately after the false-body. For example:

int i;
cin >> i
if(i >= 0)
    cout << "(positive) ";
else
    cout << "(negative) ";
cout << "i = " << i << endl;

Depending on the sign of the user’s input, this will print either

(positive) i = ...

or

(negative) i = ...

Note that one, and only one, of true-body or false-body is always run.

Let’s look at some examples:

if-else chains

Suppose we want to check whether the user’s input is 0, positive, or negative. We can start by checking for 0-or-not:

int i; 
cin >> i;
if(i == 0)  
    cout << "Zero" << endl;
else
    ...

What do we put in the else part? Well, we still need to check whether the input is positive or negative, so why not another if? Remember that although control structures are not statements, they can be used anywhere where a statement is required, so we can replace the false-body with another if-else:

int i; 
cin >> i;
if(i == 0)  
    cout << "Zero" << endl;
else
    if(i > 0)
        cout << "Positive" << endl;
    else
        cout << "Negative" << endl;

Let’s analyze what we’re doing here:

When we write a “chain” of if-else if-else like this, it’s traditional to write it like this:

int i; 
cin >> i;
if(i == 0)  
    cout << "Zero" << endl;
else if(i > 0)
    cout << "Positive" << endl;
else
    cout << "Negative" << endl;

This makes it clear that we are testing a sequence of conditions, looking for the first one that is true; that is the body that will be executed, and all the others will be skipped.

Remember that an else if will only be tried if none of the conditions before it succeeded. For example, consider this:

if(score < 60)
    grade = 'f';
else if(score < 70)
    grade = 'd';
else if(score < 80)
    grade = 'c';
else if(score < 90)
    grade = 'b';
else if(score <= 100)
    grade = 'a';

What would happen if we reversed the order of the conditions and bodies:

if(score <= 100)
    grade = 'a';
else if(score < 90)
    grade = 'b';
else if(score < 80)
    grade = 'c';
else if(score < 70)
    grade = 'd';
else if(score < 60)
    grade = 'f';

Now, everyone gets an A! The order in which the tests occur matters, if the conditions “overlap” at all. If we want to make it so the order doesn’t matter, we have to make it so the conditions don’t overlap:

if(score <= 100 && score >= 90)
    grade = 'a';
else if(score < 90 && score >= 80)
    grade = 'b';
else if(score < 80 && score >= 70)
    grade = 'c';
else if(score < 70 && score >= 60)
    grade = 'd';
else if(score < 60)
    grade = 'f';

Some other examples:

Nesting if-else structures

As the above example shows, we can put one if-else “inside” (i.e., in place of true-body or false-body) another. This is called nesting, and it’s the conditional analogue to nested loops. In the case of nested if structures, it means something like the logical AND. For example:

if(y != 0) {
    if(x > 0)
        cout << "Positive";
    else 
        cout << "Negative";
}

How can we “flatten” this into a single-level if? By examining the situations in which the different bodies are run:

So we can rewrite this as

if(y != 0 && x > 0)
    cout << "Positive";
else if(y != 0 && x <= 0)
    cout << "Negative";

We could simplify the second condition to just

if(y != 0 && x > 0)
    cout << "Positive";
else if(y != 0)
    cout << "Negative";

But can we simplify it even further, and use just an else:

if(y != 0 && x > 0)
    cout << "Positive";
else
    cout << "Negative";

No! Now the second body will run if either x <= 0 OR x == 0. It’s important, when looking at a bare else, to consider when exactly it will run. For example:

if(a > 10 && a < 20)
    ...
else
    ...

When does the else branch run? When the condition is not true, in other words, when

!(a > 10 && a < 20)
!(a > 10) || !(a < 20)
a <= 10 || a >= 20

We can use DeMorgan’s laws to negate compound conditions, to find out when the else side will apply.

Suppose we have

if(a > 10)
    if(b > 20)
        cout << "Yes";
  else
      cout << "no";

To which if is the else attached? Is it

if(a > 10)
    if(b > 20)
        cout << "Yes";
    else
        cout << "no";

or is it

if(a > 10)
    if(b > 20)
        cout << "Yes";
else
    cout << "no";

The first; the rule is that an else is attached to the closest preceding if. But note that in general, it’s clearer to use a block to make it explicit:

if(a > 10) {
    if(b > 20)
        cout << "Yes";
    else
        cout << "no";
}

Now there’s no possibility of confusion.

Transforming logical operators into nested if-else

(and vice versa)

If we have

if(A && B)
    ...

we can transform this into

if(A)
    if(B)
        ...

(and vice versa)

If we have

if(A)
    true-body
else
    false-body

we can transform this into

if(!A)
    false-body
else
    true-body

These two can be applied to nested structures. E.g.,

if(a > 1)
    if(b < 0)
        if(name.empty())
            if(c == 'y')
                ...

We can simplify this into

if(a > 1 && b < 0 && name.empty() && c == 'y')
    ...

Finally, we can always simplify:

if(true)
    true-body
else
    false-body

to just

true-body

and likewise,

if(false)
    true_body
else
    false_body

simplifies to just

false_body

Combine with DeMorgan’s laws, this gives us a lot of leeway for rewriting if-else structures to make things clear.

Examples:

if(!(x != y || y != z || x == z)
    cout << x;
else
    cout << "nope"

DeMorganifying the condition gives:

if(x == y && y == z && x != z)
    cout << x;
else
    cout << "nope";

But if we examine the condition, we see that it is always false (because if x == y and y == z it must also be the case that x == z), so we can simplify this to just

cout << "nope";

Suppose we have

if(a < b && b < c)
    cout << "Hello";
else if(c > a)
    cout << "Goodbye";
else
    cout << "OK";

How can we simplify this? Note that c > a is equivalent to a < c. And if a < b and b < c then it must be the case that a < c. So the second if is redundant, it’s covered by the first, and is thus unreachable. We can remove it.

The switch-case control structure

Fairly often we find ourselves writing a long if-else-if-… chain that looks like this:

if(a == 1)
    // a1 case
else if(a == 2)
    // a2 case
else if(a == 3)
    // a3 case
else
    // otherwise case

where every condition tests the same variable for equality with something.

An if chain like this is better written as a switch statement. A switch statement tests a single variable for equality against a set of cases.

The structure of a switch statement is quite flexible (and easy to get wrong) so we’ll look at the most useful version, corresponding to the above version

switch(a) {
    case 1:
        // a1 case
        break;

    case 2:
        // a2 case
        break;

    case 3: 
        // a3 case
        break;

    default:
        // otherwise case
}

Note that the “body” of each case is not a block but rather a sequence of statements ending with a break;. (The default must come last, and does not need a break.)

You might wonder what the break is for. The answer is that the default behavior of switch, without any break is quite unintuitive. Consider the following:

switch(a) {
    case 1:
        cout << "one";
    case 2:
        cout << "two";
    case 3:
        cout << "three";
    default:
        cout << "other";
}
cout << endl;

What will be printed if a == 1? You might think just one, but you’d be surprised. In fact, what will be printed is “onetwothreeother”. Each case falls through to the next, without checking it’s condition, unless terminated by a break. Thus, you should usually end every case (except default) with a break, to make sure this doesn’t happen unintentionally. The one situation where this “fall through” is useful is when translating something like this:

if(a == 1 || a == 2 || a == 3)
    cout << "small";
else if(a == 10)
    cout << "medium";
else
    cout << "large";

We can translate this into

switch(a) {
    case 1:
    case 2:
    case 3:
        cout << "small";
        break;

    case 10:
        cout << "medium";
        break;

    default:
        cout << "large";
}

But note that in this situation, the cases that fall through don’t do anything. In effect, several cases “share” a single body. This is the one really acceptable use for fall through.

There’s a big catch when it comes to using switch: the switch variable must be either int, or something that can be converted to an int. This means that you cannot do

float f;
switch(f) { ... }

or

string s;
switch(s) { ... }

In practice, the only things you can really switch on are int, char, and bool (although the last one is kind of silly).

You can, if you want, use if-else and loops inside a case. And likewise, you can use a switch-case within a loop, or if-else. The latter is much more common than the former. In general, it’s wise to avoid nesting control structures too deeply. Instead, split things up using functions, which we’ll look at in a bit.

Examples: