Home‎ > ‎CS 11M‎ > ‎

Complex Decisions and Functions

Questions, Answers and Review

  • Questions from last class?
  • Questions about labs or upcoming homework?

Multiple Alternatives

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

Program For an Arcade Love Tester  


#include <ArduinoSTL.h>

using namespace std; 

const int redLED = 9; 
const int greenLED = 10; 
const int blueLED = 11; 

const int squeezeSensor = A1; 

void setup() {
  Serial.begin(9600);
}

void loop() {
  int pre
ssure = analogRead(squeezeSensor); 
  if (pressure < 100) {
    // do nothing
  }
  else if (pressure < 500) {
    cout << "Just Friends." << endl;
  }
  else if (pressure < 800) {
    cout << "In Love." << endl;
  }
  else {
    cout << "Ouch!" << endl;
  }
  delay(500);
}

Choosing Between Alternatives

  • This program has four alternatives to choose from:
    1. Please hold my hand.
    2. Just friends.
    3. In love.
    4. Ouch!!
  • Note that the order that the alternatives are checked is important
  • We can follow the alternatives in the flowchart shown below

Check Yourself

  1. In the above program, what happens if the pressure sensor reads 423?
  2. True or false? An elegant way to choose among multiple alternatives is to nest if statements in an else clause.
  3. In the above sequence of if-statements, the test condition of the first if-statement must be ________before the second if-statement executes.

When Order Matters

  • In some cases, the order of the tests is not important
    • IF all the tests used == then they can be done in any order
  • Note that in the love tester the order of the tests is important to ensure that the right results are printed

Order Is Important

  • 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 (pressure < 100) {
        // do nothing
      }
      else if (pressure < 800) {
        cout << "In Love." << endl;
      }
      else if (pressure < 500) {
        cout << "Just Friends." << endl;
      }
      else {
        cout << "Ouch!" << endl;
      }
  • This does not work because all "Just Friends" values also meet the "In love" criteria
  • Some tests will never be attempted

Importance of Using if-else-if Structure

  • Note that we cannot remove the else portion of the structure
  • 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

Check Yourself

  1. True or false? Order never matters in a sequence of if and else statements.
  2. Suppose the sensor read -1 into the love tester program. What is printed?
  3. How can the following code be simplified?
    if (price > 100) {
        discount = price - 20;
    } else if (price <= 100) {
        discount = price - 10;
    }
    

switch Statements

  • The switch statement provides an alternative to an if-else-if chain
  • Executes a section of code depending on the value of a single number
  • 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: an integer constant
    • statements: the statements to execute when the condition is met
  • Any number of case labels can be placed in any order
  • When run, the integerExpression tries to match one of the case labels
  • If a label matches then the statements after the case label start executing
  • Any value that does not match starts executing with the statement after default
  • Execution continues until the end of the switch statement or encountering 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

#include <ArduinoSTL.h>

using namespace std; 

void setup() {
  Serial.begin(9600);
}

void loop() {
  int c = Serial.read();
  switch (c) {
          
    case 'a':
      cout << "Apple" << endl;
      break;

    case 'b': 
      cout << "Bannana" << endl;
      break;

    case '\r':
    case '\n':
      cout << "Ignoring \\r or \\n" << endl;
  }   
}

When to Use switch Statements
  • Switch statements can only be used when
    • The are integers or chars 
    • You are looking for exact matches
  • Thus, switch statements are inherently less useful than if-else statements
  • Also, the syntax is no clearer than if-else statements
  • There is a reason for the limitations of the switch statement
    • The compiler can generate faster code for switch statements
    • Though you shouldn't count on it...
  • However, modern compilers are quite capable of optimizing if-else statements 
  • Be careful when 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?"

Check Yourself

  1. True or false: C++ switch statements allow testing for inequalities like < and >.
  2. True or false: the default behavior of a switch statement, to continue executing past the next label, is usually correct.
  3. True or false: programmers never forget to include a break statement when needed inside a switchstatement.

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
  • There is a different table for each marital status (decision 1)
  • Once you have found the right table, you 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

Program Computing Single and Married Tax Rates

#include <ArduinoSTL.h>

using namespace std; 

void setup() {
  Serial.begin(9600);
}

void loop() {
  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;

  int marital_status; 
  double income;
  double tax;
  
  cout << "What is your income?" << endl;
  cin >> income;
  
  cout << "Please enter 1 for single, 2 for married:" << endl;
  cin >> marital_status;

  if (marital_status == 1) {
    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 << endl;
}

Check Yourself

  1. True or false? You can nest if statements within another if statement.
  2. If you are single and your taxable income is $21,450, your tax is ________.
  3. If you get a $1000 per year raise and now make $22,450, you now pay taxes of ________.
  4. Some people object to higher taxes for higher incomes, claiming that you might end up with less money after taxes when you get a raise for working hard. The flaw in this argument is ________.

4.2.5: Boolean Variables

  • Sometime we need to evaluate a logical condition in one part of a program and use it elsewhere
  • To store a condition that can only be true or false, we use a Boolean variable
  • Boolean variables are named after George Boole (1815-1864), a pioneer in the study of logic
  • We specify a Boolean variable using the bool type, which can hold just one of two values: true orfalse
    bool isCool = true;
    bool lies = false;
    
  • Question: What type of tests does George Boole give? (answer)
  • Question: How does George Boole order lunch? (see here)

Test Conditions and Boolean Values

  • Remember that test conditions always evaluate to true or false
    if (num > 0)
    
  • Thus we can use a boolean variable as a test condition
    bool isPositive = (num >= 0);
    if (isPositive)
    
  • Note that we do not need to add a relational expression to a boolean variable, like:
    if (isPositive == true) // avoid!
  • Since the boolean variable already evaluates to true or false, adding the == true is redundant
  • Likewise, we do not need to use:
    if (isPositive != false) // avoid!
  • If we want to reverse the test condition, we can use the not (!) operator
    if (!isPositive)
  • We can see the use of a Boolean variable in the following example

Example Application Using a Boolean Variable

#include <ArduinoSTL.h>

using namespace std; 

void setup() {
  Serial.begin(9600); 
}

void loop() {
  double num;
  cout << "Enter a number:" << endl;
  cin >> num;

  bool isPositive = (num >= 0);
  cout << "The test evaluated to: " << isPositive << endl;
  if (isPositive) {
    cout << "The number was 0 or positive" << endl;
  }else{
    cout << "The number was negative" << endl;
  }
}

Check Yourself

  1. True or false: the number of values that can be stored in a Boolean data type are 3: true, false and null.
  2. True or false: the Boolean data type is spelled boolean (with a lower case "b") in C++.
  3. True or false: a test condition always evaluates to a Boolean value.
  4. True or false: a Boolean variable can substitute for a test condition in an if-statement.

4.2.6: Logical Operators

  • Sometimes we need to test for multiple conditions in our programs
  • For example, we want to test if an age is between 18 and 25
  • We need to test both that age >= 18 and age <= 25
  • One way to make the tests is with nested if statements
    int age = 0;
    cout << "Enter your age:" << endl;
    cin >> age;
    if (age >= 18)
    {
        if (age <= 25)
        {
            cout << "Correct age!" << endl;
        } else {
            cout << "Wrong age!" << endl;
        }
    } else {
        cout << "Wrong age!" << endl;
    }
    
  • If the age entered is correct, like 19, we get the message, "Correct age!"
  • If the age entered is not correct, like 15, we get the message, "Wrong age!"
  • While this works, it is cumbersome to code and read
  • A better approach is to combine test conditions with logical operators

Combining Test Conditions with Logical Operators

  • logical operator, or Boolean operator, is an operator that treats operands as Boolean values (true orfalse)
  • C++ has several logical operators, but we only need to use three to create any possible test condition
  • These three operators are andor 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

Truth Tables for and, or and not

and (&&) Operator Truth Table
If expr1 is... And expr2 is... Then expr1 and expr2 is... Example Result
true true true 5 < 10 and 5 > 2 true
true false false 5 < 10 and 5 < 2 false
false true false 5 > 10 and 5 > 2 false
false false false 5 > 10 and 5 < 2 false
or (||) Operator Truth Table
If expr1 is... || expr2 is... Then expr1 or expr2 is... Example Result
true true true 5 < 10 or 5 > 2 true
true false true 5 < 10 or 5 < 2 true
false true true 5 > 10 or 5 > 2 true
false false false 5 > 10 or 5 < 2 false
not (!) Operator Truth Table
If expr is... Then ! expr is... Example Result
true false !true false
false true !(5 < 2) true

Example Using Logical Operators

  • We could rewrite our age test using an and (&&) operator like this:
    int age = 0;
    cout << "Enter your age:" << endl;;
    cin >> age;
    if (age >= 18 && age <= 25)
    {
        cout << "Correct age!\n";
    } else {
        cout << "Wrong age!\n";
    }
  • Notice that the code is shorter and it is easier to follow the logic
  • Another way to use logical operators to test the age is:
    int age = 0;
    cout << "Enter your age:" << endl;
    cin >> age;
    if (age < 18 || age > 25)
    {
        cout << "Correct age!\n";
    } else {
        cout << "Wrong age!\n";
    }
  • 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; we have to think carefully and test our conditions

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))
  • However, 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

More Information

Check Yourself

  1. Of the following groups ________ is larger.
    1. Students wearing denim
    2. Students wearing denim AND corrective lenses
  2. Of the following groups ________ is larger.
    1. Students wearing denim
    2. Students wearing denim OR corrective lenses
  3. Of the following groups ________ is larger.
    1. Students wearing denim
    2. Students wearing denim AND NOT corrective lenses
  4. For the following code, the test condition evaluates to ________.
    bool denim = true;
    bool lenses = false;
    cout << denim && lenses;
    
  5. For the following code, the test condition evaluates to ________.
    int age = 21;
    cout << (age >= 18 && age <= 25);
    
  6. Of the following logical expressions, the test to see if x is between 1 and 10 (including 1 and 10) is________.
    1. (x >= 1 && x <= 10)
    2. (1 <= x and x <= 10)
    3. (x >= 1 || x <= 10)
    4. (1 <= x or x <= 10)

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! ***";
    } else {
        cout << "Sorry, that is not correct.";
    }
    
  • 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.ino: In function `void loop()":
    guess: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" << endl;
    } else {
        cout << "b is NOT between a and c" << endl;
    }
    
  • Your code may compile and run but give incorrect results
  • The test condition is evaluated by the computer from left to right
  • The first condition is a < b which evaluates to 0 (false)
  • The second condition is then 0 < c which evaluates to 1 (true)
  • Since the whole test condition evaluates to true you get an incorrect result
  • 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" << endl;
    } else {
        cout << "b is NOT between a and c" << endl;
    }
    

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:" << endl;
    cin >> guess;
    if (guess == 7 || 8) {
        cout << "*** Correct! ***";
    } else {
        cout << "Sorry, that is not correct.";
    }
    
  • The test condition is evaluated by the computer from left to right
  • The left hand side is (guess == 7) which can evaluate to either true or false
  • The right hand side is 8, which is interpreted as true by C++
  • Since (something or true) is always true, then the test condition always evaluates to true
  • Instead, the correct way is to use || as follows:
    int guess;
    cout << "Enter a guess:" << endl;
    cin >> guess;
    if (guess == 7 || guess == 8) { cout << "*** Correct! ***" << endl; } else { cout << "Sorry, that is not correct." << endl; }

Check Yourself

  1. Can you spot the error in the following code fragment?
    if (person = terrorist) {
        punish_severely();
    } else {
        return 0;
    }
  2. What is wrong with the following string of inequalities and how do you correct the code?
    int a = 5, b = 1, c = 10;
    if (a < b < c) {
        cout << "b is between a and c" << endl;
    } else {
        cout << "b is NOT between a and c" << endl;
    }
    
  3. What is wrong with the following string of logical operators and how do you correct the code?
    int guess;
    cout << "Enter a guess:" << endl;
    cin >> guess; if (guess == 7 || 8) { cout << "*** Correct! ***" << endl; } else { cout << "Sorry, that is not correct." << endl; }

Introduction to Functions

  • Some of the setup() and loop() functions in our programs have been getting lengthy and complicated
  • The biggest problem in developing software is managing the complexity of programs
  • We can improve our code by organizing it into smaller pieces known as functions
  • Functions are a key tool in creating easy-to-understand programs that can be changed without problems

What is a Function?

  • As developers, we need to know how to write and call functions

    Function: a named block of statements that can receive input, perform an action, and optionally return a value

  • Functions are like little programs in our larger program
  • We give each little function something we want it to do
  • We then call the function and cause it to be executed from anywhere within our program
  • When the function has finished running, program execution returns to the point just after the code that called the function

Check Yourself

  1. True or false: people write functions to organize code into small understandable pieces.
  2. Which of the following are features of a function?
    1. Must have a name
    2. Can receive input
    3. Performs an action
    4. Must return a value
  3. True or false: when a function finishes executing, the program flow returns to the point just after the code that called the function.

Defining a Function

  • In this section we look at the syntax of function definitions and examine a simple example function
  • After we understand the syntax we can write more complicated functions

Function Syntax

  • The general syntax for defining a function is:
    returnType functionName(parameter1, ..., parametern) {
        statements
    }
    
  • Where:
    • returnType: the data type of the value returned
    • functionName: the name you make up for the function
    • parameterx: the input values, if any
    • statements: the list of statements to execute when the function is called
  • Can you identify each of these syntax items in the functions we have always used?
    void setup() {
      // put your setup code here, to run once:
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
    }

Why Use a Function

  • Functions make it easy to reuse code 
  • When some code does something really useful we might want to use it in several places. 
  • Functions are parameterized so they can work on different input. 

Exercise: A Resistor Color Code Function 

In this exercise you will write a key function from this weeks project. The function will contain a complex if/else if/else statement that you will need to use and reuse in the project. 

Starter Code

#include <ArduinoSTL.h>

using namespace std; 

void setup() {
  Serial.begin(9600);
}

void loop() {
  string color; 
  cout << "Enter a resistor color code." << endl;
  cin >> color; 

  int colorNumber = getNumberFromCode(color);
  cout << "That code corresponds to the number " << colorNumber << endl;
}

int getNumberFromCode(string code) {
  int number = 0;

  // Your code goes here. It should assign number. 

  return number;
}

Specifications

  1. You must write code that recognizes the following codes: 
    B   Black
    Br  Brown
    R   Red
    O   Orange
    Y   Yellow
    G   Green
    Bu  Blue
    V   Violet
    Gy  Gray
    W   White
  2. The two letter codes may have the second letter as UPPER CASE or lower case. Your program must accept both forms. For example,

    Br or BR
     

  3. In your function you should assign the variable number.
  4. Notice that number is returned to the caller.
  5. Submit your code as decode_resistor.ino
  6. You will use this code exactly in your project! 

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))
    
  • 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)
  • Which should be written as:
    if (guess == 7)
  • Another common problem is trying to use a string of inequalities without a logical operator separating each condition, like:
    if (a < b < c)
  • Which should be written as:
    if (a < b && b < c)
  • Another common error is trying to use logical operators without enough operands:
    if (guess == 7 || 8)
  • Which should be written as:
    if (guess == 7 || guess == 8)

Wrap Up and Reminders

  • For the next homework, see the schedule
  • When class is over, please shut down your computer.
  • Complete unfinished exercises from today before the next class meeting
Comments