Home‎ > ‎CS 11M‎ > ‎

Functions In Depth

Questions, Answers and Review

  • Questions from last class?
  • Questions about homework?

Review of Functions

  • When we reuse a block of code in various parts of a program, we should put the code into a function
  • The biggest problem in developing software is managing the complexity of programs
  • Organizing code into functions redcuces repeated code and overall, the complexity of larger programs

Function Syntax

Parameters and Arguments

  • When we define a function, we want it to be reusable
  • To make a function more reusable, we avoid hard-coding important values
  • Instead, we pass the key values by defining parameters
  • When we call the function, we supply an argument for each parameter, like:

    Passing arguments in a function call

  • The argument gets copied to the function parameter when making the function call

Variable and Parameter Scope

  • Variables declared in one function are not available in any other function
  • Trying to use a local variable outside a function causes a compile-time error
  • Local variables are variables declared inside of any function
  • Global variables are variables declared outside of any function
  • Parameters are a type of local variable

Returning Values

  • If a function does not return a value, we use the return type: void
  • Otherwise, we specify the type of data we want to return
  • When functions return a value, the value replaces the function call like:

    Returning values from a function call

  • Notice that normally we can return only one value from a function

Check Yourself

  1. Which of the following are NOT a reason to write a function?
    1. Allow code reuse
    2. Reduce repeated code
    3. Force the use of a single variable
    4. Logically organize code
  2. Of the following, the valid function definition is ________.
    1. fun() { /* C/C++ statements */ }
    2. int fun;
    3. int fun() { /* C/C++ statements */ }
    4. int fun();
  3. To received input, a function has __________.
  4. If a function has three parameters, a function call must include ________ arguments.
    1. 0
    2. 1
    3. 2
    4. 3
  5. True or false: A variable that can be used only within the function in which it was declared is known as a local variable.
  6. To return a value from a function use a __________ statement.

Programming Style Requirements for Functions

  • Let us look more closely at the layout of the function shown above
  • Note the placement of the curly braces
  • There are two common styles of curly brace placement for functions:
    1. Place the opening brace on the same line as the function heading:
      void myFunction() {
          // statements of the function
      }
      
    2. Place the opening brace under and lined up with the first letter of the return type:
      void myFunction()
      {
          // statements of the function
      }
      
  • You can use either style as long as you are consistent
  • Also notice the indentation of the statements inside the function
  • As before, always indent after an opening curly brace
  • After the closing curly brace, you no longer indent the extra spaces
  • Indenting makes it easier to see the block of code
  • As we have seen before, the Auto Format (Ctrl + T) function does the indenting for us

Naming Functions

  • When making up a name for a function, use verbs since functions perform an action
  • There are two common naming styles you may use:
    1. Start with a lower-case letter and use uppercase letters as separators. Do not use underbars ('_'):
      int myFunction()
    2. Use all lower case letters and use underbars ('_') as separators:
      int my_function()

Commenting Functions

  • For your homework, every function besides setup() and loop() must have a Function Comment Block before the function
  • Comments are for human readers, not compilers
  • There is no universal standard for comment layout, but we use a style commonly used with many programming languages:
    1. Block comments start with /** and ends with */
    2. The first line explains the idea of the function, not the implementation
    3. An @param entry explains each parameter
    4. An @return entry describes the return value
  • The following example has a fully commented function

Example Program with Fully Commented Function

#include <ArduinoSTL.h>

/**
  CS-11M Function Example
  Purpose: Multiplies two ints obtained from the user.

  Author: Mike Matera
  Date: 10/19/2016
*/

using namespace std; 

const int BAUD_RATE = 9600;

void setup() {
  Serial.begin(BAUD_RATE);
  cout << "Enter two numbers separated by a space" << endl;
}

void loop() {
  int a; 
  int b;
  int c; 
  cin >> a >> b; 
  c = myMultiplyFunction(a, b);
  cout << a << " * " << b << " = " << c; 
}

/**
  Multiply two numbers and return the result.
  
  @param x first quantity to multiply.
  @param y second quantity to multiply.
  @return result of the multiplication operation
*/
int myMultiplyFunction(int x, int y) {
  int result;
  result = x * y;
  return result;
}

Further Information

Check Yourself

  1. Of the following, ________ is an acceptable curly-brace placement and statement indentation style for function definitions.
    1. int fun() { /* C++ statements */ }
    2. int fun()
      { /* C++ statements */ }
    3. int fun() {
      /* C++ statements */ }
    4. int fun()
      {
           /* C++ statements */
      }
  2. True or false: functions names should start with lower-case letters.
  3. True or false: the first line of a function block comment explains the idea of the function, not the implementation.
  4. For every function parameter, you include a(n) ________ tag in the function block comment.
  5. If the function returns a value, you include a(n) ________ tag in the function block comment.

Overloading Functions

  • C++ lets us have more than one function with the same name
  • Having two or more function definitions with the same name is known as function overloading
  • Overloaded functions must have different parameter types between them
  • As an example, consider our previous example function
    int myMultiplyFunction(int x, int y) {
      int result;
      result = x * y;
      return result;
    }
    
  • The above function only uses integer multiplication
  • We could add a second function to handle floating-point multiplication, like:
    double myMultiplyFunction(double x, double y) {
      int result;
      result = x * y;
      return result;
    }
    
  • Now we will get different results when multiplying numbers with decimal points
    3.50 * 4.50 = 15.00
    3 * 4 = 12
    

Example Sketch with Overloaded Functions

#include <ArduinoSTL.h>

using namespace std; 

const int BAUD_RATE = 9600;

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

void loop() {
  cout << "2 x 4 = " << myMultiplyFunction(2, 4) << endl;
  cout << "2.5 x 4.5 = " << myMultiplyFunction(2.5, 4.5) << endl << endl;
  delay(10000);
}

int myMultiplyFunction(int x, int y) {
  cout << "I'm the integer version" << endl;
  int result;
  result = x * y;
  return result;
}

double myMultiplyFunction(double x, double y) {
  cout << "I'm the double version" << endl;
  int result;
  result = x * y;
  return result;
}

Overloading Resolution

  • Let us look at how function calls are matched with overloaded functions
  • The process the compiler follow is:
    1. Look for an exact signature match
    2. Look for a "compatible" signature
  • If an exact match is not found, C++ will try to convert the data type as follows:
    1. byte, char, unsigned char, and short are promoted to int
    2. unsigned short can be promoted to int if it can hold its entire value range or unsigned int otherwise
    3. float is promoted to a double
  • Some conversions may cause a loss of precision, so care must be taken
  • Also, C++ only knows how to convert certain types, mostly the numeric types
  • Thus, C++ does not automatically convert something like a String to a double
  • Conversions fail when they are ambiguous:
    • myMultiplyFunction(2, 4.5)  # ERROR, ambiguous
    • myMultiplyFunction(2.5, 4)  # ERROR, ambiguous

Type Conversion

  • C++ can convert between numerical types
  • We can manually specify a type conversion by using a typecast
  • To typecast, we place the desire type in parenthesis in front of the variable, like:
    double c = myMultiplyFunction((int) a, (int) b);
    
  • The above calls the myMultiplyFunction(int x, int y) function even though a and b are of type double
  • Another way to convert types is to use the conversion functions listed on the Arduino Language Reference page
  • Also, to convert a String to a numeric type, use one of the String object functions

Avoiding Function Overloading

  • Usually we make numeric parameters type double
  • Type double will accept any numeric type
  • However, we will come across cases where we need to overload functions

Check Yourself

  1. Of the following function signatures, the two that are not allowed in the same scope are ________.
    1. int add(int a, int b)
    2. double add(int a, double b)
    3. double add(double a, double b
    4. double add(int x, int y)
  2. The reason the two function signature of the above are not allowed in the same scope is that ________.
    1. function names are the same
    2. parameter types are different
    3. parameter types are the same
    4. return types are different
  3. For the following function call, the function definition that is invoked is ________.
    fun(98, 99);
    
    1. void fun(int n, double m)
    2. void fun(double n, int m)
    3. void fun(int n, int m)
    4. void fun(double n, double m)

Value Parameters

  • There are two ways to pass arguments to parameters
  • All our functions so far have used value parameters
  • Value parameters are separate from the variables in the calling function
  • Modification of value parameters does not affect the original caller's value when the function returns

How Value Parameters Work

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

Example of Value Parameters

#include <ArduinoSTL.h>

using namespace std; 

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

void loop() {
  string wrd; 
  cout << "Enter a word." << endl;
  cin >> wrd; 

  mystery(wrd);

  cout << "The word is \"" << wrd << "\"" << endl;
}

void mystery(string m) {
  string first = m.substr(0, 1);
  string second = m.substr(1, m.length()-1);
  m = second + first + "ay";
}

Check Yourself

  1. True or false: the function gets a copy of the original information (argument).
  2. True or false: the function returns the new calculation.
  3. True or false: the original variable from the caller (written on the caller's paper) does not change.

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 the caller's existing variable instead
  • Any change to a reference parameter's value 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

#include <ArduinoSTL.h>

using namespace std; 

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

void loop() {
  string wrd; 
  cout << "Enter a word." << endl;
  cin >> wrd; 

  mystery(wrd);

  cout << "The word is \"" << wrd << "\"" << endl;
}

void mystery(string &m) {
  string first = m.substr(0, 1);
  string second = m.substr(1, m.length()-1);
  m = second + first + "ay";
}

Call-By-Reference Details
  • What's really passed to the reference parameter?
  • A reference to the caller's original argument!
  • Essentially a reference parameter is another name for the original argument
  • With a reference parameter the code uses the original argument variable in calculations
  • Any change made to a reference parameter changes the original argument
  • Because C++ is passing variables, arguments for reference parameters must be variables and not literal numbers or other constants

Check Yourself

  1. True or false: When an argument is passed to a reference parameter, the actual variable is sent to the function.
  2. True or false: when a variable is used as a call-by-reference argument, the value of the variable is never changed by the function call.
  3. True or false: you cannot pass a literal value to a call-by-reference parameter.
  4. What must a reference parameter refer to in a function call?
    1. A variable
    2. An expression
    3. A return statement
    4. A constant

Mixed Parameter Lists

  • Parameter lists can include both value and reference parameters
  • As usual, the order of arguments in the list is critical
  • The following is a function signature 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

Check Yourself

  1. True or false: you can include both call-by-value and call-by-reference parameters in the same function definition.
  2. True or false: the order of the parameters determines which arguments are passed by value or reference.
  3. In the following code snippet, the argument passed by reference is ________.
    fun(arg1, arg2, arg3);
    //...
    void fun(int par1, int par2&, double par3)
    
    1. arg1
    2. arg2
    3. arg3
    4. none of these
  4. In the previous question, we can tell which argument is call by reference because of the ________.

When to Use Reference Parameters

  • Reference parameters are usually 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, function calls using reference parameters usually operate faster
  • However, reference parameters restrict the arguments we can use for a function
  • Specifically, we must use a variable argument and not a literal or constant value
  • Usually, the best practice is to pass an object, like a String, by reference
  • Also, we should use value parameters unless a function needs to modify a parameter
  • The performance advantage of reference parameters for primitive types tends to be negligible
  • Thus, it is not worth restricting the call pattern of a function

Check Yourself

  1. True or false: it is always better to use call by reference because of the efficiency gains.
  2. Call-by-reference is more efficient than call-by-value because ________.
    1. less data is transferred for complex types
    2. memory is a list addresses, which is easier for the computer to process
    3. we can pass literal values to the function
    4. we can pass constants to the function
  3. True or false: call-by-reference places more restrictions on arguments to functions, such as prohibiting literal values as arguments.

Exercise 1: Exploring Call by Reference 

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

Starter Code

#include <ArduinoSTL.h>

using namespace std; 

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

void loop() {
  int a; 
  int b; 
  cout << "Enter two integers." << endl;
  cin >> a >> b; 
  cout << "Before: a == " << a << ", b == " << b << endl;
  swap(a, b);
  cout << " After: a == " << a << ", b == " << b << endl;
}

void swap(int var1, int var2) {
    int temp = var1;
    var1 = var2;
    var2 = temp;
}

Specifications

  1. Start the Arduino IDE, copy the starter code below and paste it into the main IDE window.
  2. Save the project using the name swap (File > Save As...) to a convenient location like the Desktop or the Arduino projects folder.
  3. Compile the sketch to verify you copied the starter code correctly.

    When compiling is successful, you will see a message at the bottom of the source code window saying, "Done compiling."

  4. Upload and run the sketch. When you run the program, the output should look like this:
    Before: a == 1, b == 2 After: a == 1, b == 2

    Notice that a and b have the same values before and after calling the function swap(). Any value assigned to var1 and var2 have no effect on a and b..

  5. Change your program by adding the two ampersands (&) highlighted below:

    The ampersands tell C++ to use call-by-reference when passing parameter values.

  6. 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:

    Before: a == 1, b == 2  After: a == 2, b == 1

    Notice that a and b have different values before and after calling the function swap(). Any value assigned to var1 and var2 change a and b respectively.

  7. Save your swap.ino file to submit to Canvas with the next homework.

When finished, please help those around you.

Summary

Overloading
  • C++ lets us have more than one function with the same name
  • Having two or more function definitions with the same name is known as function overloading
  • Overloaded functions must have different parameter types between them
  • Since there is more than one function with the same name, the compiler searches for the correct match as follows:
    1. Look for an exact signature match
    2. Look for a "compatible" signature
  • If an exact match is not found, C++ will try to automatically convert the data type
  • However, C++ only knows how to convert certain types, mostly the numeric types
  • Thus, C++ does not automatically convert something like a String to a double
  • We can manually specify the conversions by using a typecast
  • To typecast, we place the desire type in parenthesis in front of the variable, like:
    double a = 1.23, b = 2.34;
    double c = myMultiplyFunction((int) a, (int) b);
    
  • The above calls the myMultiplyFunction(int x, int y) function even though a and b are of type double
Reference Parameters
  • There are two ways to pass arguments to parameters:
  • Value parameters are separate from the variables in the calling function
  • Modification of value parameters does not affect the original value
  • Call-by-references works differently than call-by-value
  • A reference parameter does not create a new variable, but refers to the caller's 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 literal values or other 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 like a string to a function
    • We need to return more than one value

User Input Functions

Using Functions to Read Input

  • One of the purposes of functions is to organize code into logical blocks or chunks
  • One useful chunk is getting user input
  • The following code shows an example of how to validate user input
  • The code won't proceed until the user inputs a correct value
  • The code also checks for problems with cin
#include <ArduinoSTL.h>

using namespace std;

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

void loop() {
  cout << "Enter a number between 1 and 100" << endl;
  float num;
  bool okay = false;
  while (! okay) {
    cin >> num;
    if (cin.fail()) {
      string junk;
      cin >> junk;
      cin.clear();
    }else{
      if (num <= 100 and num >= 1) {
        okay = true;       
      }
    }    
  }
  cout << "Number is " << num << endl;
}
  • In the loop() function we verify that the input is a number between 1 and 100 inclusive.
  • If the input is not positive, we use a loop to make the user re-enter the number
  • In addition, we use two if-statements to validate the input. 
  • Only when the user enters a corret number, does the loop exit and the function return

Exercise 2: Adding Input Functions

Input validation is cumbersome. In this exercise you will create a function that performs input validation. 

Specifications

  1. Start the Arduino IDE with the input validation example above.
  2. Add a inputDouble() function to the sketch with the following signature: 

    double inputDouble(string prompt, double min, double max)

  3. Take input in your main loop using your function and print the user input.
  4. Compile and upload your sketch, and verify it works correctly.
  5. If you have problems, ask a classmate or the instructor for help.
  6. Save your updated input_validation.ino file to submit to Canvas with the next homework.

When finished, please help those around you.

Wrap Up and Reminders

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