7: Advanced Control Flow

What We Will Cover


Continuations

Homework Questions?

Questions from last class?

Problem 1: What is output by the following program? (Do not run the code -- work it out by hand)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int mystery(int param);

int main() {
    int num = 2;

    cout << "At first, num=" << num << endl;
    int result = mystery(num);
    cout << "After calling, num=" << num << endl;
    cout << "And result=" << result << endl;

    return 0;
}

int mystery(int param) {
    cout << "param=" << param << endl;
    param = param * 2;
    return param;
}
  1. At first, num=2
    param=2
    After calling, num=4
    And result=4
    
  2. At first, num=2
    param=4
    After calling, num=4
    And result=4
    
  3. At first, num=2
    param=2
    After calling, num=2
    And result=4
    
  4. None of these

7.1: Advanced Conditions

Learner Outcomes

At the end of the lesson the student will be able to:

  • Recognize the correct ordering of tests in multiple branches
  • Program conditions using Boolean operators and variables
  • Avoid some common pitfalls when creating test conditions

7.1.1: Multiple Alternatives

  • By using collections of if-else statements, a program can distinguish between multiple alternatives
  • For example, consider the following example from the textbook p. 244 where a user enters the name of a coin, and the program displays the value

Program Converting Coin Names to Values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

int main() {
    cout << "Enter coin name: ";
    string name;
    cin >> name;
    double value = 0;

    if (name == "penny") {
        value = 0.01;
    } else if (name == "nickel") {
        value = 0.05;
    } else if (name == "dime") {
        value = 0.10;
    } else if (name == "quarter") {
      value = 0.25;
    } else {
        cout << name << " is not a valid coin name\n";
    }
    cout << "Value = " << value << "\n";

    return 0;
}

Choosing Between Alternatives

  • This program has five alternatives to choose from:
    1. "penny"
    2. "nickel"
    3. "dime"
    4. "quarter"
    5. erroneous input
  • Note that the order that the alternatives are checked is unimportant
  • We can follow the alternatives in the flowchart shown below

Flowchart of Multiple Alternative for Converting Coins to Values

Flowchart of multiple alternatives

7.1.2: When Order Matters

  • In some cases, the order of the tests is important
  • For example, look at the following program from the textbook that displays a description of the likely impact of an earthquake based on its magnitude on the Richter scale

Program Showing Multiple Alternatives Where Order Matters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

int main() {
    cout << "Enter a magnitude on the Richter scale: ";
    double richter;
    cin >> richter;

    if (richter >= 8.0) {
        cout << "Most structures fall\n";
    } else if (richter >= 7.0) {
        cout << "Many buildings destroyed\n";
    } else if (richter >= 6.0) {
        cout << "Many buildings considerably damaged, "
             << "some collapse\n";
    } else if (richter >= 4.5) {
        cout << "Damage to poorly constructed buildings\n";
    } else if (richter >= 3.5) {
        cout << "Felt by many people, no destruction\n";
    } else if (richter >= 0) {
        cout << "Generally not felt by people\n";
    } else {
        cout << "Negative numbers are not valid\n";
    }

    return 0;
}

Order Is Important

  • Note that the order of the tests is important to ensure that the right results are printed
  • If we rearranged the order of the if-else statements, we would get the wrong results
  • For example, if we reversed the order of the tests:
    if (richter >= 0) { // tests in wrong order
        cout << "Generally not felt by people\n";
    } else if (richter >= 3.5) {
        cout << "Felt by many people, no destruction\n";
    } else if (richter >= 4.5) {
        cout << "Damage to poorly constructed buildings\n";
    } else if (richter >= 6.0) {
        cout << "Many buildings considerably damaged, "
             << "some collapse\n";
    } else if (richter >= 7.0) {
        cout << "Many buildings destroyed\n";
    } else if (richter >= 7.0) {
        cout << "Most structures fall\n";
    } else {
        cout << "Negative numbers are not valid\n";
    }
    
  • This does not work because all values meet the first condition
  • Every other test will never be attempted

Importance of Using if-else-if Structure

  • Note that we cannot remove the else portion of the structure like shown below:
    if (richter >= 7.0) { // Does not use else
        cout << "Most structures fall\n";
    }
    if (richter >= 7.0) {
        cout << "Many buildings destroyed\n";
    }
    if (richter >= 6.0) {
        cout << "Many buildings considerably damaged, "
             << "some collapse\n";
    }
    if (richter >= 4.5) {
        cout << "Damage to poorly constructed buildings\n";
    }
    if (richter >= 3.5) {
        cout << "Felt by many people, no destruction\n";
    }
    if (richter >= 0) {
        cout << "Generally not felt by people\n";
    }
    if (richter < 0) { {
        cout << "Negative numbers are not valid\n";
    }
    
  • The conditions must be exclusive and we need the else-if conditions to ensure exclusivity
  • Independent if statements may cause a single input to print several messages

7.1.3: switch Statements

  • The switch statement provides an alternative to an if-else-if chain
  • Executes a section of code depending on value of a variable
  • The general syntax is:
    switch (integerExpression) {
       case label1:
          statements
          break;
       case label2:
          statements
          break;
       ...
       case labeln:
          statements
          break;
       default:
          statements
    }
    
  • Where:
    • integerExpression: an arithmetic expression that resolves to an integer number
    • labelx: a numeric constant
    • statements: the statements to execute when the condition is met
  • Any number of case labels can be placed in any order
  • Any value that does not match starts executing with the statement after default
  • Execution continues until the end of the switch statement or a break statement
  • The break statement causes an immediate exit from the switch statement
  • Just as case identifies possible starting points, break determines end points

Example Program Using a switch Statement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;

int main() {
    int shippingOption = 0;
    cout << "Enter a shipping option (1-3): ";
    cin >> shippingOption;

    switch (shippingOption - 1) {
        case 0:
            cout << "Parcel post delivery in two weeks.\n";
            break;
        case 1:
            cout << "UPS delivery in three days.\n";
            break;
        case 2:
            cout << "Fed-Ex overnight delivery.\n";
            break;
        default:
            cout << "It may never get there!\n";
    }
    cout << "Ship it!\n";

    return 0;
}

When to Use switch Statements

  • You can only use switch statements with integer expressions and each case must be a constant
  • In addition, switch statements only work for exact matches (==)
  • Thus, switch statements are inherently less useful than if-else statements
  • Also, the syntax is no clearer than if-else statements
  • Note that there is a reason for the limitations of the switch statement
  • Many years ago a compiler could generate more efficient code (using jump tables or binary searches) only within the limitations of the switch statement
  • However, modern compilers are quite capable of optimizing if-else statements to the same degree
  • Thus, we have no reason to ever use a switch statement, depending on your compiler
  • On the other hand, there are reasons to avoid using a switch statement
  • Every branch of the switch statement must be terminated by a break statement
  • If the break statement is missing, the program falls through and executes the next case without testing
  • There are rare uses for this fall through behavior, such as printing the words for the song, The Twelve Days of Christmas
  • However, according to a study by Peter van der Linden, reported in his book, Expert C Programming, p. 38, the falling through behavior is needed less than 3% of the time
  • Thus, the default behavior is wrong 97% of the time
  • Forgetting to type the break statement is a very common error and the source of many bugs
  • So one has to ask oneself, "Why use an inferior programming statement that causes more problems than it solves?"

Exercise 7.1

In this exercise we test multiple alternatives in a program. As an example, we will calculate a student's letter grade according to the following table:

Numerical Grade Letter Grade
greater than or equal to 90 A
less than 90 but greater than or equal to 80 B
less than 80 but greater than or equal to 70 C
less than 70 but greater than or equal to 60 D
less than 60 F

Specifications

  1. Copy the following program into a text editor, save it as grader.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <iostream>
    using namespace std;
    
    int main() {
        // Enter your code here
    
        return 0;
    }
    
  2. Add code to get user input into a variable named score. When you run the program after adding this code, the output should look like:
    Enter a score: 95.7
    

    Make sure you declare the variable with a compatible data type. Note that the underlined numbers above shows what the user enters. For more information see section 2.4.6: Input and Output.

  3. First we will look at a series of if statements and see that if statements alone are not enough to solve this problem. Copy the following into your program after the input statements:
    string grade;
    if (score >= 90) {
        grade = "A";
    }
    if (score >= 80) {
        grade = "B";
    }
    if (score >= 70) {
        grade = "C";
    }
    if (score >= 60) {
        grade = "D";
    }
    if (score < 60) {
        grade = "F";
    }
    cout << grade << endl;
    

    Compile and run your modified program. There is a logic problem with this code. Each test condition needs to work over a range of values rather than with a single value.

  4. One way to correct the problem is to nest an if statement inside of another if statement. To see how this works, modify your code to add nested if statements as shown below:

    Nested if statements

    To test the range, the outer if statement tests the lower condition and the inner if statement tests the upper condition. For more information see section 7.1.4: Nested Branches.

  5. Compile and run your modified program to make sure you made the changes correctly. When you run the program, the output should look like:
    Enter a score: 80
    B
    

    Run your program a few times with different score to verify that any score displays the correct letter grade.

  6. The reason for using nested if statements is often to test multiple conditions. Often, a better alternative is to use Boolean operators like && and ||. To see how Boolean operators work, modify your code to add nested if statements as shown below:

    Testing with logical operators

    For more information see section 7.1.5: Logical Operators.

  7. Compile and run your modified program to make sure you made the changes correctly. When you run the program, the output should look like:
    Enter a score: 80
    B
    

    Run your program a few times with different score to verify that any score displays the correct letter grade.

  8. De Morgan's laws lets you use an || for the conditions if you reverse the inequalities and precede the test condition with a ! operator. For example, the following is equivalent to the previous example:

    Modifying the test with De Morgan's law

    Note that this test is more confusing to human's because of the ! (negation) operator. If you have complicated tests such as this, you may be able to use De Morgan's laws to simplify the conditions. For more information see section 7.1.6: De Morgan's Laws.

  9. Perhaps the most elegant solution is to nest an if statement in the else clause of the preceding if. Modify the series of if statements to include an else clause as shown below:

    if-else statements

    We are nesting if statements in the else clause. Nesting in the else clause makes each test condition of the if statement exclusive of the others because each test condition eliminates all the preceding conditions. Thus, in this scenario the order is important. For more information see section 7.1.2: When Order Matters.

  10. Compile and run your modified program to make sure you made the changes correctly. When you run the program, the output should look like:
    Enter a score: 80
    B
    

    Run your program a few times with different score to verify that any score displays the correct letter grade.

  11. Submit your final program source code to Blackboard as part of assignment 7.

Check Yourself

As time permits, be prepared to answer these questions. You can find more information by following the links after the question.

  1. True of false? An elegant way to choose among multiple alternatives is to nest if statements in an else clause. (7.1.1)
  2. If score = 85, what is output by the following code fragment? (7.1.1)

    if-else statements

  3. True of false? Order never matters in a sequence of if and else statements. (7.1.2)
  4. What is the difference between if-else if-else and nested if statements? (7.1.2 vs. 7.1.4, answer)
  5. True of false? A switch statement is a more powerful solution to multiple if-else if-else statements (7.1.3)
  6. When does an AND (&&) of two or more conditions evaluate to true? (7.1.5)
  7. When does an OR (||) of two or more conditions evaluate to false? (7.1.5)
  8. What is the effect of the NOT (!) operator? (7.1.5)
  9. True of false? A && B is the same as B && A for any Boolean conditions A and B. (7.1.5)
  10. If score = 85, what is output by the following code fragment? (7.1.5)

    Testing with logical operators

  11. What is the simplified form of each of these using DeMorgan's law? (7.1.6, answer):
    1. !(x > 0 && y > 0)
    2. !(x != 0 || y != 0)
    3. ! (country == "USA" && state != "HI" && state != "AK")
    4. !(x % 4 != 0 || !(x % 100 == 0 && x % 400 != 0))
  12. How many errors can you spot in the following code fragment? (7.1.7)
    if (person = terrorist) {
        punish_severely();
    } else {
        exit(-1);
    }
    

    Answer and credit for the idea.

  13. What is wrong with the following string of inequalities and how do you correct the code? (7.1.7)
    int a = 5, b = 1, c = 10;
    if (a < b < c) {
        cout << "b is between a and c\n";
    } else {
        cout << "b is NOT between a and c\n";
    }
    
  14. What is wrong with the following string of logical operators and how do you correct the code? (7.1.7)
    int guess;
    cout << "Enter a guess: ";
    cin >> guess;
    if (guess == 7 || 8) {
        cout << "*** Correct! ***\n";
    } else {
        cout << "Sorry, that is not correct.\n";
    }
    

7.1.4: Nested Branches

  • Nested if-else statements can be used when there are two (or more) levels of decision making.
  • For example, consider the following two tax tables from 1992 shown in the textbook on p. 252
  • There is a different table for each marital status (decision 1)
  • Once you have found the right table, your are taxed differently according to your income (decision 2)

Tax Table if Single

If your status is Single and if the taxable income is over but not over the tax is of the amount over
$0 $21,450 15% $0
$21,450 $51,900 $3,217.50 + 28% $21,450
$51,900 $11,743 + 31% $51,900

Tax Table if Married

If your status is Married and if the taxable income is over but not over the tax is of the amount over
$0 $35,800 15% $0
$35,800 $86,500 $5,370.00 + 28% $35,800
$86,500 $19,566.00+ 31% $86,500

Programming Two-Level Decisions

  • When we program this two-level decision process, we often use two levels of if statements
  • We say the income test is nested inside the test for filing status
  • We can see this two-level decision in the flowchart shown below
  • Also, we can examine and run the program from the code shown below
  • Note that more complicated decisions may require deeper levels of nesting

Flowchart of Two-level Tax Decision Process

Flowchart of nested branches

Program Computing Single and Married Tax Rates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
using namespace std;

int main() {
    const double SINGLE_LEVEL1 = 21450.00;
    const double SINGLE_LEVEL2 = 51900.00;

    const double SINGLE_TAX1 = 3217.50;
    const double SINGLE_TAX2 = 11743.50;

    const double MARRIED_LEVEL1 = 35800.00;
    const double MARRIED_LEVEL2 = 86500.00;

    const double MARRIED_TAX1 = 5370.00;
    const double MARRIED_TAX2 = 19566.00;

    const double RATE1 = 0.15;
    const double RATE2 = 0.28;
    const double RATE3 = 0.31;

    double income;
    double tax;

    cout << "Please enter your income: ";
    cin >> income;

    cout << "Please enter s for single, m for married: ";
    string marital_status;
    cin >> marital_status;

    if (marital_status == "s") {
        if (income <= SINGLE_LEVEL1) {
            tax =  RATE1 * income;
        } else if (income <= SINGLE_LEVEL2) {
            tax = SINGLE_TAX1
                + RATE2 * (income - SINGLE_LEVEL1);
        } else {
            tax = SINGLE_TAX2
                + RATE3 * (income - SINGLE_LEVEL2);
        }
    } else {
        if (income <= MARRIED_LEVEL1) {
            tax =  RATE1 * income;
        } else if (income <= MARRIED_LEVEL2) {
            tax = MARRIED_TAX1
                + RATE2 * (income - MARRIED_LEVEL1);
        } else {
            tax = MARRIED_TAX2
                + RATE3 * (income - MARRIED_LEVEL2);
        }
    }
    cout << "The tax is $" << tax << "\n";

    return 0;
}

7.1.5: Logical Operators

  • An operator that combines test conditions is called a logical operator or Boolean operator
  • Logical operators allow you to consider multiple cases when deciding on what code to execute
  • Being able to consider multiple cases gives us more flexibility in creating test conditions
  • C++ has several logical operators, but you only need to use three to create any possible Boolean expression
  • These three operators are and, or and not, which are discussed below
  • These logical operators are traditionally written as && (and), || (or) and ! (not)
  • Both variants are legal under ANSI C++
  • The words are easier to read but many C++ programmers still use the older form

and Operation

  • One such operator is and, which is traditionally spelled && in C++
  • For example, the following is true if guess != GUESS1 and guess != GUESS2:
    ((guess != GUESS1) && (guess != GUESS2))
    
  • When two boolean expressions are connected using && the whole expression is true only if both conditions are true
  • However, if either condition is false the whole expression is false

or Operation

  • Also, you can combine two boolean expressions using the or operator, traditionally spelled || in C++
  • For example, the following is true if GUESS1 == guess or GUESS2 == guess:
    ((GUESS1 == guess) || (GUESS2 == guess))
    
  • When two boolean expressions are connected using || the whole expression is true if either condition is true
  • Only if both conditions are false is the entire expression false

not Operation

  • Additionally, you can negate any expression using not operator, traditionally spelled ! in C++
  • To use it, place the entire expression in parenthesis and place the ! operator in front
  • For example, to allow two guesses, we can negate our previous example:
    !((GUESS1 == guess) || (GUESS2 == guess))
    

Parenthesis

  • Remember that a boolean expression in an if statement must be enclosed in parenthesis
  • Thus, an if statement with && might look like:
    if ((guess != GUESS1) && (guess != GUESS2))
  • Note that relational operators have a higher precedence than logical operators
  • Thus, we can remove the inner parenthesis without affecting the meaning:
    if (guess != GUESS1 && guess != GUESS2)
  • However, if using parenthesis is easier to understand then use the extra parenthesis

Confusing && and || Conditions

  • Many people confuse && and || conditions, especially when learning about logical operators
  • A value lies between 0 and 100 if the value is at least 0 and at most 100
  • A value is outside that range if it is less than 0 or greater than 100
  • There is no golden rule; you have to think carefully and test your conditions

Example Program with Logical Operators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

int main() {
    const int ANSWER1 = 7;
    const int ANSWER2 = 8;

    int guess = 0;
    cout << "I'm thinking of a number between"
         << " 1 and 10.\nCan you guess it?\n\n"
         << "Enter your guess: ";

    while (guess != ANSWER1 && guess != ANSWER2) {
        cin >> guess;

        if (ANSWER1 == guess || ANSWER2 == guess) {
            cout << "*** Correct! ***\n";
        } else {
            cout << "Sorry, that is not correct.\n";
            cout << "Try again: ";
        }
    }
    return 0;
}

7.1.6: De Morgan's Laws

  • Suppose we want to charge a higher shipping rate if we do not ship to the continental United States
  • We might code a conditional statement like:
    if (!(country == "USA"
            && state != "AK"
            && state != "HI")) {
        shipping_charge = 20.00;
    }
    
  • This test is somewhat complicated and we have to think carefully through the logic
  • What makes the test complicated is the multiple not ('!') operators
  • Humans generally have a hard time comprehending logical conditions with not operators ('!') applied to and/or expressions
  • DeMorgan's Law can be used to simplify these Boolean expressions
  • DeMorgan's Law has two forms:
    • !(A && B) is the same as !A || !B
    • !(A || B) is the same as !A && !B
  • Pay particular attention to the fact that the && and || operators are reversed by moving the ! (not) inwards
  • For example:
    state != "AK" && state != "HI"
    means:
    !(state == "AK") && !(state == "HI")
  • Applying DeMorgan's Law to the original expression yields:
    !(state == "AK" || state == "HI")
  • Applying DeMorgan's Law to our original problem:
    !(country == "USA"
        && state != "AK"
        && state != "HI"))
    
    which is equivalent to:
    !(country == "USA"
        || !(state != "AK")
        || !(state != "HI"))
    
    Which in turn yields the simpler test:
    country != "USA"
        || state == "AK"
        || state == "HI"
    

More Information

7.1.7: Conditional Pitfalls

  • Unfortunately, you can write many things in C++ that should be incorrect but end up working for some obscure reason
  • This means that you can code something that should create an error message but does not
  • Thus, a program may compile and run with no error messages but still be wrong
  • Since you may not realize that it is wrong, it can be hard to find and correct these types of errors

Using = Instead of ==

  • One common mistake is to use = when you meant to use ==
  • For example, look at the test condition in the following code:
    if (guess = 7) {
        cout << "*** Correct! ***\n";
    } else {
        cout << "Sorry, that is not correct.\n";
    }
    
  • Notice that the condition is really an assignment statement and not a test
  • You would think that it would fail to compile -- but it does not
  • However, it will not work as you might expect
  • A way to prevent this type of problem is to reverse the order of your test condition:
    if (7 = guess) {
  • Now the compiler will give you an error message and your code will not compile:
    guess.cpp: In function `int main()':
    guess.cpp:10: error: non-lvalue in assignment
    
  • However, if you correctly use == then your code will compile
    if (7 == guess) {

Strings of Inequalities

  • Do not use a string of inequalities like the following:
    int a = 5, b = 1, c = 10;
    if (a < b < c) {
        cout << "b is between a and c\n";
    } else {
        cout << "b is NOT between a and c\n";
    }
    
  • Your code will probably compile and run but give incorrect results
  • Instead, the correct way is to use && as follows:
    int a = 5, b = 1, c = 10;
    if (a < b && b < c) {
        cout << "b is between a and c\n";
    } else {
        cout << "b is NOT between a and c\n";
    }
    

Strings of Logical Operators

  • Logical expressions often read like "normal" English.
  • However, C++ requires more exactness than English
  • For example, the following code will compile and run but give wrong results:
    int guess;
    cout << "Enter a guess: ";
    cin >> guess;
    if (guess == 7 || 8) {
        cout << "*** Correct! ***\n";
    } else {
        cout << "Sorry, that is not correct.\n";
    }
    
  • Instead, the correct way is to use || as follows:
    int guess;
    cout << "Enter a guess: ";
    cin >> guess;
    if (guess == 7 || guess == 8) {
        cout << "*** Correct! ***\n";
    } else {
        cout << "Sorry, that is not correct.\n";
    }
    

7.1.8: Summary

  • By using collections of if-else statements, a program can distinguish between multiple alternatives
  • Sometimes the order of statements is important for our program to work correctly
  • We must think carefully and test our conditions rigorously
  • Nested if-else statements can be used when there are two (or more) levels of decision making.
  • We looked at an example of tax tables, filing as single or married
  • To create conditions with multiple cases, we looked at using logical operators: &&, || and !
  • We looked at some examples including:
    ((guess != GUESS1) && (guess != GUESS2))
    ((GUESS1 == guess) || (GUESS2 == guess))
    !((GUESS1 == guess) || (GUESS2 == guess))
    
  • Humans generally have a hard time comprehending logical conditions with not operators ('!') applied to and/or expressions
  • DeMorgan's Law can be used to simplify these Boolean expressions
  • DeMorgan's Law has two forms:
    • !(A && B) is the same as !A || !B
    • !(A || B) is the same as !A && !B
  • Pay particular attention to the fact that the and or operators are reversed by moving the not inwards
  • Unfortunately, you can write things in C++ that should be incorrect but end up working for some obscure reason
  • These types of errors are often very difficult to find
  • One common mistake is to use = when you mean to use ==
  • if (guess = 7) {
  • Another problem is trying to use a string of inequalities without a logical operator separating each condition
    • if (a < b < c) {
    • Which should be written as:
    • if (a < b && b < c) {
  • Another error is trying to use logical operators without enough operand
  • if (guess == 7 || 8) {
  • Which should be written as:
  • if (guess == 7 || guess == 8) {

7.2: Advanced Loops

Learner Outcomes

At the end of the lesson the student will be able to:

  • Discuss when to use which C++ looping statement
  • Process user input in a loop
  • Read input from a file through redirection
  • Discuss why to avoid break and continue statements with loops

7.2.1: Comparing C++ Looping Statements

  • Recall that C++ has three looping statements:
    • for: special purpose counting loop
    • while: general purpose loop
    • do-while: like the while but testing at the end of the loop
  • When we design a loop, we need to consider these things:
    1. Loop body -- which statements to repeat
    2. Test condition -- when to loop and when to stop
    3. Test update -- what must change to exit the loop
    4. Initialization code -- how to start the loop correctly
  • The for loop provides a syntax for these things in a compact form:
    for (initialization; test; update) {
        // statements to repeat
    }
    
  • Where:
    • initialization: statement to execute before the loop starts
    • test: condition to evaluate at the start of each iteration
    • update: statement to execute at the end of the loop
  • The for loop is primarily used for counting:
    for (int i = 0; i < 10; i++) {
        cout << i << endl;
    }
    
  • The simpler while loop has the following syntax:
    while (test) {
       // statements to repeat
    }
    
  • Where test is the condition to evaluate at the start of each iteration
  • Because of its simpler form, the while loop is considered a more general purpose loop
  • However, we still have to include the four parts of a loop design:
    string repeat = "y"; // initialization
    while ("y" == repeat) { // test condition
        // statements to repeat
        cin >> repeat; // update
    }
    
  • Another looping statement is the do-while loop, which tests the condition at the end of the loop body:
    do {
       // statements to repeat
    } while (test); //test condition
    
  • Where test is the condition to evaluate at the end of each iteration
  • Testing at the end ensures a minimum of at least one iteration
  • Thus we use the do-while loop when we want to ensure at least one iteration
  • For example:
    string repeat; // no initialization needed
    do {
        // statements to repeat
        cin >> repeat; // update
    } while ("y" == repeat); // test condition
    

Which Loop Statement to Use

  • Generally, we use for statements when counting with a loop
  • For non-counting loops where we want to ensure at least one iteration, use a do-while statement
  • Otherwise, use the simpler while statement
  • Notice that all loops have the same test condition
  • This makes it possible always to convert one looping statement into another with some adjustment to other code
  • Thus, choosing which loop statement to use is more a matter of elegance than a requirement

7.2.2: Nested Loops

  • More advance looping applications often have loops nested within other loops
  • For example, you may use a nested loop to print a table of values
  • The following example shows a simple table created with nested loops
  • Let's follow the execution sequence before checking the result

Example of Nested Loops

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main() {
    for (int outer = 1; outer < 4; outer++) {
        for (int inner = 1; inner < 4; inner++) {
            cout << outer << " " << inner << endl;
        }
    }
}

Tracing the Variables

  • Below is a trace of the variables for the inner and outer loops
  • Note that the outer loop changes only after the inner loop is finished
Memory  Screen 
 int outer   int inner   
  1   1 1 1
    2 1 2
    3 (end of loop)   1 3
  2   1 2 1
    2 2 2
    3 (end of loop)   2 3
  3 (end of loop)     1 3 1
    2 3 2
    3 (end of loop)   3 3

7.2.3: Accumulating Values

  • Consider a program to sum series of numbers from the keyboard
  • The program uses a variable (sum) to accumulate values entered from the keyboard each time through the loop

Example Program to Sum a Series of Numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

int main() {
    double input = 1;
    double sum = 0;

    cout << "I will add up numbers for you\n\n";
    while (input != 0) {
        cout << "So far, sum = " << sum << endl;
        cout << "Enter a number or 0 to exit: ";
        cin >> input;
        sum = sum + input;
    }
    cout << "Ending sum: " << sum << endl;

    return 0;
}

Redirection of Input and Output

  • We could use the above program by typing numbers at the command line
  • However, that quickly gets tedious
  • A better way is to use redirection of input
  • The command line interfaces of most operating systems have a way to link a file to the input of a program
  • The content of the file gets fed into the program as if all the characters had been typed by a user
  • For example, after compiling the above program we type something like the following at the command line:
    ./sum < input.txt
    
  • Where input.txt is a text file containing all the scores
  • We can redirect program output to a file as well using something like:
    ./sum > output.txt
    
  • We can combine input and output redirection in one command:
    ./sum < input.txt > output.txt
    

7.2.4: Using break and continue Statements with Loops

  • We saw the use of the break statement in the switch statement
  • Another use the break statement is to terminate loops
  • The continue statement is similar except that instead of exiting a loop, it jumps to the end of the loop
  • The causes the loop to skip the rest of the current iteration and start the next iteration
  • The following code shows an example of break and continue
  • Note that using break and continue statements with loops is generally consider poor programming style
  • The reason we are reviewing them is to see what NOT to do

Example Using the break and continue Statements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

int main() {
    int count = 0;
    while (true) {
        count++;
        if (count == 3) {
            continue;
        }
        cout << count << endl;
        if (count >= 5) {
            break;
        }
    }
    cout << "After count= " << count << endl;
}

When to Use break and continue Statements

  • Use break statements in switch statements only
  • For this course, never use break or continue statements in looping constructs:
    • Poor coding habit
    • Loops should only have one exit point
  • Note that the textbook on p. 137 has an interesting story about a major problem caused by the use of break statements
  • On January 15, 1990, a misused break statement caused an AT&T 4ESS telephone switch to fail
  • The failure propagated through the entire U.S. network, rendering it nearly unusable for about nine hours
  • A programmer had used a break to terminate an if statement
  • However, break cannot be used to break out of an if
  • Therefore the program execution broke out of the enclosing switch statement instead
  • Perhaps more importantly, using a break statement makes it difficult to prove program correctness in critical software

7.2.5: Summary

  • C++ has three looping statements:
    • for: special purpose counting loop
    • while: general purpose loop
    • do-while: like the while but testing at the end of the loop
  • The for loop is primarily used for counting:
    for (int i = 0; i < 10; i++) {
        cout << i << endl;
    }
    
  • We use the do-while loop when we want to ensure at least one iteration, like:
    string repeat; // no initialization needed
    do {
        // statements to repeat
        cin >> repeat; // update
    } while ("y" == repeat); // test condition
    
  • Otherwise, use the simpler while statement:
    string repeat = "y"; // initialization
    while ("y" == repeat) { // test condition
        // statements to repeat
        cin >> repeat; // update
    }
    
  • Sometimes you need to nest one loop inside another
  • We looked at an example of building a table using nested loops
  • The outer loop iterated through the columns while the inner loop iterated through the row
  • We looked at how to process user input using a loop
  • Also, we looked at how to use a file with input redirection
  • We can redirect program input from a file, program output to a file or both:
    scores < input.txt
    scores > output.txt
    scores < input.txt > output.txt
    
  • In addition, we looked at how to use break and continue with loops
  • Then we discussed why you should not use break and continue with loops

Exercise 7.2

In this exercise we explore the uses of loops to process user input.

Specifications

  1. Copy the following program into a text editor, save it as scores.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <iostream>
    using namespace std;
    
    int main() {
        // Enter your code here
    
        return 0;
    }
    
  2. Declare two variables of type double named sumScores and nextScore and initialize the variables to 0. In addition, declare an integer variable named count and initialize it to 0. Compile your code to make sure you declared the variables correctly.
  3. Now we want to use a loop to enter a series of scores. Add the following code after the variables you declared:
    do {
        count++;
        cout << "Enter score #" << count << ": ";
        cin >> nextScore;
    
        if (nextScore >= 0) {
            sumScores += nextScore;
        }
    } while (nextScore >= 0);
    

    Compile your code to make sure you added the loop correctly.

  4. The loop includes statements to collect the sum of the scores in the variable named sumScores. Add a statement after the loop to print sumScores to the console. When you run the program after adding this code, the output should look like:
    Score 1: 38
    Score 2: 39
    Score 3: -1
    
    Sum of scores: 77
    

    The loop uses the sumScores variable to accumulate scores during each repetition of the loop. For more information see lesson: 5.1.5: Accumulating Values.

  5. Each assignment can have a different value and we need to allow for the different values. First you will need to add two variables of type double named sumValues and nextValue after the other variables. Then update the loop with the following code:

    Summing scores

    Compile your code to make sure you updated the loop correctly. Notice how one if statement is nested inside another. For more information see section: 7.1.4: Nested Branches. Also notice the compound condition joined by the logical operator &&. For more information see section: 7.1.5: Logical Operators

  6. The loop has statements to collect both the sum of the scores in the variable named sumScores and the sum of the values in the variable named sumValues. Add a statement after the loop to print both sumScores and sumValues to the console. When you run the program after adding this code, the output should look like:
    Score 1: 38
    Value of score 1: 40
    Score 2: 39
    Value of score 2: 40
    Score 3: -1
    
    Sum of scores: 77
    Sum of values: 80
    
  7. The grade for all the scores is calculated by dividing the sum of the scores by the sum of the values. Add these statements after the other print statements:
    double percentage = sumScores / sumValues * 100;
    cout << "Grade percentage: " << percentage << endl;
    
    When you run the program after adding this code, the output should look like:
    Score 1: 38
    Value of score 1: 40
    Score 2: 39
    Value of score 2: 40
    Score 3: -1
    
    Sum of scores: 77
    Sum of values: 80
    Grade percentage: 96.25
    
  8. One problem with our program is the user can still enter letters instead of digits. We can prevent this error by checking cin.fail() and looping until the user enters a correct value. We implement this loop in a function to make a reusable module by using the following code:

    Function inputDouble

    Compile your program to make sure you added the new function correctly. Notice how the while loop of inputDouble() is nested inside the do-while loop of main(). Nesting loops is often confusing and so we sometimes put the inner loop in a separate function. For more information see section: 7.2.2: Nested Loops.

  9. To use the inputDouble() function, we need to change two statements in main. Change cin >> nextScore; to:
    nextScore = inputDouble();
    Also, change cin >> nextValue; to
    nextValue = inputDouble();
  10. Compile and run your modified program to make sure you made the changes correctly. When you run the program, the output should look like:
    Score 1: thirty-eight
    Error: please enter a number: 38
    Value of score 1: forty
    Error: please enter a number: 40
    Score 2: 39
    Value of score 2: 40
    Score 3: -1
    
    Sum of scores: 77
    Sum of values: 80
    Grade percentage: 96.25
    
  11. Next we use input and output redirection to both enter values into and save the output of your program. Save the following file to the same directory as your program source code:

    input.txt

    Then run your compiled program by typing the following at the command line:
    ./scores < input.txt > output.txt
    

    Open output.txt in a text editor like TextPad and notice how scrambled the input statements look. However, the output should look normal. For more information see section: 7.2.3: Accumulating Values.

  12. Submit both your final program source code and output.txt to Blackboard as part of assignment 7.

Check Yourself

As time permits, be prepared to answer these questions. You can find more information by following the links after the question.

  1. What are the advantages of using a for loop compared to other looping statements? (7.2.1)
  2. What is the difference between a while and a do-while loop? (7.2.1)
  3. When should you use each type of loop statement supported by C++? (7.2.1)
  4. What is a nested loop? (7.2.2)
  5. What is input and output redirection and when should you use it? (7.2.3)
  6. Why should you avoid using break and continue statements with loops? (7.2.4)

7.3: Reference Parameters

Learner Outcomes

At the end of the lesson the student will be able to:

  • Explain the difference between call-by-value and call-by reference parameter passing
  • Return values from functions using call-by-reference

7.3.1: Parameter Passing and Value Parameters

  • There are two ways to pass arguments to parameters
  • All our functions so far have used value parameters
  • Value parameters are separate variables from the ones in main() (or another calling function)
  • Modification of value parameters does not affect the original value

How Value Parameters Work

  • During the function call, your program copies the argument value into the parameter variable
  • The scope of value parameters is the same as for a local variable
  • If modified, only the local copy changes
  • When the function returns, your program discards any value assigned to the parameter
  • The following example program uses value parameters
  • What does this program output?

Example of Value Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int mystery(int param);

int main() {
    int num = 2;

    cout << "At first, num=" << num << endl;
    int result = mystery(num);
    cout << "After calling, num=" << num << endl;
    cout << "And result=" << result << endl;

    return 0;
}

int mystery(int param) {
    cout << "param=" << param << endl;
    param = param * 2;
    return param;
}

7.3.2: Reference Parameters

  • C++ has another parameter-passing mechanism known as call-by-reference
  • A reference parameter does not create a new variable, but refers to an existing variable instead
  • Any change in a reference parameter is actually a change in the variable to which it refers
  • We create a reference parameter by using an ampersand '&' between the parameter's type and name
    parameterType& parameterName
    
  • The following program shows an example of reference parameters
  • What is different?
  • What does this program output?

Example of Reference Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int mystery(int& param);

int main() {
    int num = 2;

    cout << "At first, num=" << num << endl;
    int result = mystery(num);
    cout << "After calling, num=" << num << endl;
    cout << "And result=" << result << endl;

    return 0;
}

int mystery(int& param) {
    cout << "param=" << param << endl;
    param = param * 2;
    return param;
}

Call-By-Reference Details

  • What's really passed to the reference parameter?
  • A reference to the caller's original argument!
  • A reference is the memory address of a variable
  • Note that arguments for reference parameters must be variables and not constants

7.3.3: Mixed Parameter Lists

  • You can combine parameter-passing mechanisms in one function
  • Parameter lists can include both value and reference parameters
  • As usual, the order of arguments in the list is critical
  • The following is the function prototype with mixed parameter types:
    void mixedCall(int& par1, int par2, double& par3);
    
  • To call the function:
    int arg1 = 0, arg2 = 1;
    double arg3 = 2.2;
    mixedCall(arg1, arg2, arg3);
    
  • arg1 must be an integer type and is passed by reference
  • arg2 must be an integer type and is passed by value
  • arg3 must be a double type and is passed by reference

7.3.4: When to Use Reference Parameters

  • Reference parameters are more efficient than value parameters because they do not make copies of the parameters:
    • A program simply passes the memory address to the function
    • No new memory space is allocated and deallocated
  • Therefore, procedure calls using reference parameters usually operate faster
  • However, reference parameters restrict the arguments you can use for your function
  • Specifically, you must use a variable argument and not a literal or constant value
  • Usually, the best practice is to pass an object by reference
  • Also, you pass a primitive type by reference only when a function needs to modify a parameter
  • Otherwise, you usually do not bother passing primitive types by reference
  • The performance advantage of reference parameters for primitive types tends to be negligible
  • Thus, it is not worth restricting the call pattern of your function
  • The following example uses a reference parameter for the object type and a value parameter for the primitive type

Example Program Using Mixed Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "turtlelib.cpp"

void drawSquare(Turtle& turtle, double size);

int ccc_win_main() {
    Turtle myrtle;
    myrtle.right(90);

    drawSquare(myrtle, 3);
    myrtle.moveTo(-6, -6);
    drawSquare(myrtle, 5);

    return 0;
}

void drawSquare(Turtle& turtle, double size) {
    const double ANGLE = 90;

    turtle.forward(size);
    turtle.left(ANGLE);
    turtle.forward(size);
    turtle.left(ANGLE);
    turtle.forward(size);
    turtle.left(ANGLE);
    turtle.forward(size);
    turtle.left(ANGLE);
}

Exercise 7.3

In this exercise we explore how call-by-reference parameters differ from call-by-value parameters.

Specifications

  1. Type the following program into a text editor and save it as swap.cpp:

    Program using pass by value

  2. Compile and run the starter program to make sure you entered it correctly. When you run the program, the output should look like this:
    Enter two integers: 1 2
    After calling function: 1 2
    

    Notice that num1 and num2 have the same values before and after calling the function swap(). Any value assigned to var1 and var2 have no effect on num1 and num2. For more information, see section: 7.3.1: Parameter Passing and Value Parameters.

  3. Change your program by adding the four ampersands (&) circled below:
  4. Program using pass by reference

    The ampersands tell C++ to use call-by-reference when passing parameter values. For more information, see section: 7.3.2: Using Reference Parameters.

  5. Compile and run the modified program to make sure you made the changes correctly. When you run the program, the output should look like this:
    Enter two integers: 1 2
    After calling function: 2 1
    

    Notice that num1 and num2 have different values before and after calling the function swap(). Any value assigned to var1 and var2 change num1 and num2 respectively. For more information, see section: 7.3.2: Using Reference Parameters.

  6. Submit your final program source code to Blackboard as part of assignment 7.

Check Yourself

As time permits, be prepared to answer these questions. You can find additional information in the sections that follow.

  1. When an argument is passed to a value parameter, what gets copied? (7.3.1)
  2. When an argument is passed to a reference parameter, what gets copied? (7.3.2)
  3. How do call-by-value and call-by reference differ? (7.3.1 and 7.3.2)
  4. Can you mix call-by-value and call-by-reference in the same parameter list? (7.3.3)
  5. When should you use reference parameters? (7.3.4)
  6. When should you use value parameters? (7.3.4)

7.3.5: Summary

  • There are two ways to pass arguments to parameters
  • Value parameters are separate variables from the ones in the calling function
  • Modification of value parameters does not affect the original value
  • C++ has another parameter-passing mechanism known as call-by-reference
  • A reference parameter does not create a new variable, but refers to an existing variable instead
  • Any change in a reference parameter is actually a change in the variable to which it refers
  • We create a reference parameter by using an ampersand '&' between the parameter's type and name
  • Arguments for reference parameters must be variables, not constants
  • We can mix value and reference parameters in a function, like:
    void mixedCall(int& par1, int par2, double& par3);
    
  • We typically use reference parameters when:
    • We pass an object to a function
    • We need to return more than one value

7.4: Midterm Preparation

Learner Outcomes

At the end of the lesson the student will be able to:

  • Discuss how to prepare for the midterm exam
  • Describe how to take the midterm exam

7.4.1: About the Exam

  • You must attend the exam or you will receive a score of zero (0)
    • Except by prior arrangement with the instructor
  • I am using Blackboard to administer the test
  • Since there are more students than computers, we must take the exams in shifts
  • Shifts are determined by your last name and section as follows:
    Last Name Section Start Time
    A-G Day 10:20 AM
    H-Z Day 11:20 AM
    A-M Evening 6:00 PM
    N-Z Evening 8:00 PM
  • If you want to change shifts then make arrangements to switch with another student who is on the other shift
  • The exam is closed books and closed notes
    • However, you may have one 3" x 5" card of handwritten notes for the exam
  • You may use a computer from the classroom, but only to take the exam in Blackboard
  • You may have a sheet of blank scratch paper
  • You may NOT use the computer to compile or run programs
  • You may NOT use the computer to view documents on the Internet
  • You may NOT use a calculator or other electronic device
    • Thus, you may NOT use your own computer to take the exam
    • If you have a cell phone visible or in use during the exam, you will automatically fail
  • You may NOT communicate with anyone but the instructor during the exam

3"x5" Card Requirements

  • Put your name on your card
  • Maximum card or paper size is 3 inches by 5 inches
  • You may use both sides of the card
  • Notes must be handwritten and NOT photocopied
  • Notes cannot have any complete functions -- only code snippets
  • Any 3" x 5" cards violating these rules will be confiscated before the test
  • You must turn in your 3" x 5" card after the exam in any case

7.4.2: Recommended Preparation

  • Try the Example Midterm in Blackboard
    • These questions are intended to help you get a "feel" for taking an exam in Blackboard
    • The questions are NOT intended to tell you everything that is on the exam
  • Review the instructor's notes and make sure you can answer the Check Yourself questions, making notes about anything that you have difficulty answering
  • Review your notes and prepare your 3" x 5" card
  • Review your homework assignments and solutions
  • Review your CodeLab exercises
  • You should be prepared to write short programs
    • Mathematical expressions
    • User I/O
    • Conditional statements
    • Testing multiple conditions
    • "main" loops
    • Counting loop algorithms such as accumulating values in a loop
    • Using library functions (pow(), rand(), sqrt(), etc.)
    • Declaring and defining functions
      • Call-by-value
      • Call-by-reference
  • More quiz tips: Basic Rules For Taking a Multiple-Choice Test

7.4.3: Exam Taking Tips

  • Save after every answer -- you can change your mind and choose another
  • If you get stuck on a question, make your best guess and return later
  • If you are equally uncertain between two choices, go with first impression
  • When writing code, do NOT add more than the problem asks for
  • You do not need to comment code for tests and exams
    • Unless specifically instructed to in the exam question
  • Use the full time available
    • Check your work if you finish early

7.4.4: Questions and Answers

  • Any questions?

Wrap Up

Home | Blackboard | Announcements | Day Schedule | Eve Schedule
Course info | Help | FAQ's | HowTo's | Links
Last Updated: April 21 2009 @14:17:51