8: More About Functions

What We Will Cover


Continuations

Homework Questions?

Questions from last class?

Famous Students

8.1: More About Functions

Learner Outcomes

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

  • Describe local vs. global variables
  • Write global constants
  • Write void functions

8.1.1: Review of Functions

  • As we add more code to main(), it becomes too long to easily understand
  • The solution is to break up the long sequences of code into shorter sections using functions

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

  • Functions allow us to organize code into short reusable pieces
  • Creating functions is like adding new commands to the programming language

Defining Functions

  • To define a function, we use the following syntax:
    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
  • As an example, we wrote the function add() which is called from main():
    int add(int a, int b) {
        int sum = a + b;
        return sum;
    }
    int main() {
        //... other code omitted
        int total = add(num1, num2);
        //... other code omitted
    }
    

Parameters

  • In the parenthesis of the function are the parameters
  • Parameters are the inputs to a function
  • When we define a function, we want it to be reusable
  • To make a function more reusable, we avoid hard-wiring important values
  • Instead, we pass the key values by defining parameters
  • When we call the function, we supply an argument for each parameter as shown below

Passing Arguments to Function Parameters

Passing arguments in a function call

Returning Values

  • The first word in the function signature is the return type
    int add(int a, int b)
    
  • The return type specifies the type of data the function outputs
  • When we want the function to return a value we write a return statement
    return sum;
    
  • The returned value gets substituted for the function call in the calling code
  • The flow of a function call is shown below

Function Call Flow

Flow of control for a function call

Check Yourself

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
#include <iostream>
using namespace std;

int mystery(int param) {
    cout << "param=" << param << endl;
    param = param * 2;
    return 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;
}
  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

8.1.2: Variable Scope and Global Constants

  • Variables and parameters declared in a function can only be used within that function
  • The area of code that a variable can operate within is known as it's scope

    Scope: the part of a computer program where a variable or other programming entity can be used

  • Because of scope, we can use variables with the same name in different functions
  • For instance, the following program uses the variable area in both main() and circleArea()

Program Using Local Variables and Global Constants

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

const double PI = 3.14159265;

double circleArea(double radius) {
    double area = PI * radius * radius;
    return area;
}

int main() {
    cout << "Enter the radius of a circle: ";
    double radius = 0.0;
    cin >> radius;

    double area = circleArea(radius);
    cout << "The circle area is " << area << endl;

    return 0;
}

Local Variables

  • Variables defined inside a function are known as local variables

    Local variable: a variable accessible only within a function or block

  • When the function finishes executing, local variables disappear
  • Trying to use a local variable outside a function causes a compile error

Global Variables and Constants

  • Notice the constant variable PI in the above example:
    const double PI = 3.14159265;
    
  • C++ lets us define variables and constants with global scope
  • Global variables and constants are declared outside of any function and can be accessed in any function
  • If local variables have the same name,then local variables take precendence over global variables
  • Because they have the same value everywhere, and cannot be changed, global constants are considered good programming practice

Programming Style: No Global Variables

  • Global variables are variables that are defined outside functions
  • They are declared just like a global constant but without the const keyword
  • Unlike global constants, global variables are considered poor programming practice
  • A global variable can be modified anywhere in your program, so it is hard to trace where it changes
  • Global variables make programs more difficult to understand and maintain
  • Do not use global variables in your programs for this course
  • Instead, use function parameters and return statements to transfer data from one function to another

Group Activity

This program illustrates some of the hazards of using global variables. What is the output of the following code snippet if it was compiled and executed?

int x, y, z;
int fun(int a, int  b) {
    int x;
    x = a + 2;
    a = a * 3;
    b = x + a;
    return b;
}
int main( ) {
    x = 1;
    int y = 2;
    z = 3;
    y = fun(y, x);
    cout << x << ' ' << y << ' ' << z << endl;
    return 0;
}

More Information

Check Yourself

  1. A variable only accessible inside a function or other block is called a ________ variable.
  2. True or false: parameters are local variables.
  3. The area of a program within which a variable exists is known as its ________.
  4. True or false: global constants are acceptable programming practice whereas global variables are not.
  5. The following code displays the value ________.
    int x = 7; // problem with global variables
    int fun(int x) {
        x = 12;
        return x;
    }
    // main code here
    x = 42;
    int x = 3;
    x = fun(x);
    cout << x;
    

8.1.3: void Functions

  • Previously we looked at functions that returned one value
  • Functions returning a value use a return statement
    return result;
  • A function that returns no value is called a void function
  • In C++, void functions are defined like functions that return a value
  • However, the keyword void replaces the return type
  • For example, what do you notice that is different about the following?
    void showFToC(double fDegrees) {
        double cDegrees = 5.0 / 9 * (fDegrees - 32);
        cout << fDegrees << " degrees Fahrenheit is "
             << cDegrees << " degrees Celsius." << endl;
        return;
    }
    
  • There are only two differences between definitions for void functions and other functions:
    • void return type
    • return statement is optional and does not specify a value if used
  • If no return statement is specified, the function returns after executing the last statement of the function
  • Here is an example program using the void function shown above

Example Program With a void Function

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

void showFToC(double fDegrees) {
    double cDegrees = 5.0 / 9 * (fDegrees - 32);
    cout << fDegrees << " degrees Fahrenheit is "
         << cDegrees << " degrees Celsius." << endl;
    return;
}

int main() {
    double fTemperature;

    cout << "Enter a temperature in Fahrenheit: ";
    cin >> fTemperature;
    showFToC(fTemperature);

    return 0;
}

When to Write void Functions

  • When we use a non-void function, we are asking a question
  • The function returns a value in response to our question
    cout << sqrt(9.0);
    
  • When we use a void function, we are giving the computer a command
    showFToC(212);
  • We do not expect or receive an answer
  • Functions to display something are a good place to use void

Common Errors With void Functions

  • Note that we cannot call a void function from a cout statement
  • For example, the following causes a compile-time error:
    cout << showFToC(fTemperature); // NO!
  • The reason is that a void functions does not return a value and cout has nothing to print
  • Similarly, we cannot call a void function in an assignment statement:
    double temp = showFToC(fTemperature); // NO!
  • There is nothing to assign to the variable temp

Check Yourself

  1. True or false: return statements are optional for void functions, but not for non-void functions.
  2. We use a void function when we do not need an answer and are issuing the computer a(n) ________ .
  3. True or false: Assuming that printResults() is a void function, the following is not allowed because printResults() does not return a value.
    cout << printResults(32.5, 0.3); // not allowed
    

Exercise 8.1: Writing void Functions (6m)

In this exercise we write our first void function.

Specifications

  1. Copy the following program into a text editor and save it as squares.cpp:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #include <iostream>
    using namespace std;
    
    //Function definition goes here
    
    int main() {
        int length = 1;
    
        while (length > 0) {
            cout << "\nI will print squares for you!\n";
            cout << "Enter the length of a side (-1 to quit): ";
            cin >> length;
            //code to call function
        }
        cout << "Thanks for \"squaring\" with me!" << endl;
    
        return 0;
    }
    
  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:
    I will print squares for you!
    Enter the length of a side (-1 to quit): 5
    
    I will print squares for you!
    Enter the length of a side (-1 to quit): -1
    Thanks for "squaring" with me!
    
  3. Write a function that prints squares named printSquares().

    Your function should take in an integer argument for the length of one side of the square and should return nothing. Use the following code inside the function.

    for (int row = 1; row <= size; row++)
    {
        for (int col = 1; col <= size; col++)
        {
            cout << "*";
        }
        cout << endl; // newline before next row
    }
    
  4. Call your function inside the while loop so that it will print out a square given the user input for the length of a side.
  5. Run the program again and verify you see results like the following:
    I will print squares for you!
    Enter the length of a side (-1 to quit): 5
    *****
    *****
    *****
    *****
    *****
    
    I will print squares for you!
    Enter the length of a side (-1 to quit): -1
    Thanks for "squaring" with me!
    
  6. Submit your final program source code to Canvas as part of assignment 8.

When finished, please help those around you.

8.1.4: Arrays as Function Parameters

  • Arrays can be function parameters
  • When writing a function with an array parameter, we place an empty [] after the parameter name:
    void print(int values[], int size);
    
  • We pass the size of the array to the function so the function knows the size of the array
  • When we call the function, we do NOT include the []:
    print(data, size); // function call
    
  • The following program shows arrays used with functions

Example Program Using Arrays with Functions

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>
#include <vector>
using namespace std;

void print(int values[], int size) {
    for (int i = 0; i < size; i++) {
        cout << values[i] << endl;
    }
}

int main() {
    const int MAX_SCORES = 10;
    int scores[MAX_SCORES];

    cout << "Enter " << MAX_SCORES << " values:\n";
    for (int i = 0; i < MAX_SCORES; i++) {
        cin >> scores[i];
    }

    cout << "You entered:\n";
    print(scores, MAX_SCORES);

    return 0;
}

Returning Array Values

  • Note that arrays cannot be function return types
    int[] fun() { } // NO!
    
  • Instead, array parameters have the special property of retaining changes made to element values
  • If we assign a new value to an array element inside a function, the new value is retained when the function returns
  • For example:
    int changeElementZero(int data[]) {
        data[0] = 42;
    }
    
    int main() {
        int data[] = { 0, 0, 0, 0, 0 };
        changeElementZero(data);
        cout << data[0] << endl; // prints 42
    }
    
  • Since array parameters retain changes made inside a function, then returning an array with a return statement is not necessary

Using the const Modifier

  • Normally, a function can change the element values of array parameters
  • We may prevent the modification by adding the const modifier:
    void print(const int values[], int size);
    
  • The compiler will issue an error message if you try to change the value of an array element
  • If a function with a constant array parameter calls another function using the const array parameter as an argument, the called function must use a const array parameter as well
  • Otherwise, the compiler will issue an error

Check Yourself

  1. The problem with the following function is ________.
    double[] readItems(double data[], int cap, int& size) {
        size = 0;
        double value;
        bool more = true;
        while (more) {
            if (size < capacity) {
                cin >> value;
                data[size] = value;
                size++;
            } else {
                more = false;
            }
        return data;
    }
    
    1. the data parameter is missing the size inside the square brackets
    2. you cannot assign a value to the parameter size
    3. array parameters require a size number inside the square brackets
    4. arrays cannot be return types
  2. True or false: array parameters do not include the array size inside the square brackets.
  3. To prevent a function from modifying an array, add the ________ keyword to the parameter declaration.

8.1.5: Summary

  • As we add more code to main(), it becomes too long to easily understand
  • The solution is to break up the long sequences of code into shorter sections using functions

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

  • Functions allow us to organize code into short reusable pieces
  • Creating functions is like adding new commands to the programming language
  • To define a function, we use the following syntax:
    returnType functionName(parameter1, ..., parametern) {
        statements
    }
    
  • When defining a function we provide:
    • a name for the function
    • a parameter variable for each argument
    • a type for the result
  • If the function does not return a value, we use the return type: void
  • Otherwise, we specify the type of data we want to return

Check Yourself

Answer these questions to check your understanding. You can find more information by following the links after the question.

  1. Why do programmers write functions? (8.1.1)
  2. What is the syntax for writing functions? (8.1.1)
  3. What is meant by the term scope? (8.1.2)
  4. What is a global variable? (8.1.2)
  5. Why should you avoid using global variables in your programs? (8.1.2)
  6. What is a global constant? (8.1.2)
  7. Why might you want to use a global constant in your programs? (8.1.2)
  8. What makes a global constant acceptable whereas a global variable should be avoided? (8.1.2)
  9. When do you use the void return type in a function definition? (8.1.3)
  10. Are you required to have a return statement in a void function definition? (8.1.3)
  11. What effect would removing the return statement from the following function have on compiling or executing the program? (8.1.3)
    void showResults(double fDegrees, double cDegrees) {
        cout << fDegrees
             << " degrees Fahrenheit is equivalent to "
             << cDegrees << " degrees Celsius." << endl;
        return;
    }
    
  12. Assuming that showResults() is a void function, what is wrong with: (8.1.3)
    cout << showResults(32.5, 0.3); // not allowed
    

8.2: Function Flow and Style

Learner Outcomes

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

  • Trace the control-flow of functions calling functions
  • Document functions with good style

8.2.1: Functions Calling Functions

  • Functions may call other functions
  • Within the body of one function, we can call another function call
  • Functions can call other functions as often as needed
  • We are already doing this when main() calls a function
  • The following program writes the program status to the console as the program runs
  • Writing values like this is known as data logging
  • Using log statements is a good debugging technique

Example of Functions Calling Functions

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

void log(string funName, double value) {
    cout << "In " << funName << " the value is ";
    cout << value << endl;
}

double square(double number) {
    log("square() before", number);
    double result = number * number;
    log("square() after", result);
    return result;
}

int main() {
    double number = 5;
    log("main() before", number);
    double result = square(5);
    log("main() after", result);

    return 0;
}

Nesting Functions

  • Note that even though functions can call other functions, we cannot nest function definitions in C/C++11
    int main() {
        int add(int a, int b)  // NO!
        {
            return a + b;
        }
        // other code here
    }
    
  • If we try we get an error
    error: a function-definition is not allowed here...
    
  • Each function must be declared outside of another function

Functions Calling Each Other

  • Functions can call other functions as often as needed
  • It is also possible for a function to call a function that in turn calls the original function
  • This can lead to a compiling problem as shown in the example below
    fiddle.cpp:2:7: error: 'b' was not declared in this scope
         b();
           ^
    
  • To allow functions to call each other, we introduce function prototypes in the next section

Functions Calling Each Other

void a() {
    b(); //  error: 'b' was not declared in this scope
}

void b() {
    a();
}

int main() {
    a();
    return 0;
}

Check Yourself

  1. A function can call other functions ________.
    1. one time
    2. two times
    3. three times
    4. as many times as needed
  2. True or false: we should not try to define one function inside another.
  3. True or false: it is possible for functions to call each other.

8.2.2: Function Prototypes

  • C++ allows us to declare functions without defining them
  • Function declarations (prototypes) have the function heading without the function body
  • The general syntax for declaring a function is:
    returnType functionName(parameter1, ..., parametern);
    
  • Where:
    • returnType: the type of the value returned
    • functionName: the name you make up for the function
    • parameterx: the input values, if any
  • As an example, we can declare a function to calculate the square of a number like this:
    double square(double number);
  • By declaring a function, the compiler can resolve a function call made inside main()
  • Thus, we can reorganize our programs to place function definitions after main()
  • For now the use of function prototypes is optional
  • However, there are times in C++ when we need to use function prototypes
  • Note that if we use function prototypes, we place the block comments before the prototypes and not the definitions
  • We can see this new function organization in the following example

Example Program with a Function Prototype

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
#include <iostream>
using namespace std;

// Returns the square of a number
double square(double number);

// Displays a message to the console
void log(string funName, double value);

int main() {
    double number = 5;
    log("main", number);
    double result = square(5);
    log("main", result);

    return 0;
}

double square(double number) {
    log("square", number);
    double result = number * number;
    log("square", result);
    return result;
}

void log(string funName, double value) {
    cout << "In " << funName << "() the value is ";
    cout << value << endl;
}

Check Yourself

  1. A declaration of a function before it is defined is known as a function declaration or ________.
  2. True or false: one reason for function prototypes is to allow the programmer to write the function definition after the main() function.
  3. True or false: the main difference between a function prototype and a function definition is that the function body is missing in a function prototype.
  4. Of the following, the valid function prototype is ________.
    1. fun() { /* C++ statements */ }
    2. int fun;
    3. int fun() { /* C++ statements */ }
    4. int fun();

8.2.3: 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, you always indent 3-4 more spaces after an opening curly brace
  • After the closing curly brace, you no longer indent the extra 3-4 spaces
  • Indenting makes it easier to see the block of code

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 projects, every function must have a block comment before the function declaration
  • 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 end with */
    2. The first line explains the idea or purpose of the function, not how it is coded
    3. An @param entry explains each parameter
    4. An @return entry describes the return value
  • The following example has a fully commented function
  • Notice that the example does NOT include the program file block comment required for homework

Example Program with Fully Commented Function

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
#include <iostream>
using namespace std;

/**
    Returns the square of a number

    @param number The number to square
    @return the square of number
*/
double square(double number);

/**
    Displays a message to the console

    @param funName The name of the function
    @param value The value to display
*/
void log(string funName, double value);

int main() {
    double number = 5;
    log("main", number);
    double result = square(5);
    log("main", result);

    return 0;
}

double square(double number) {
    log("square", number);
    double result = number * number;
    log("square", result);
    return result;
}

void log(string funName, double value) {
    cout << "In " << funName << "() the value is ";
    cout << value << endl;
}

Using Doxygen

  • The purpose of the comment layout is to produce hyperlinked web pages describing your program
  • The program we use to produce the web pages is called Doxygen
  • We can run Doxygen from TextPad in the classroom and CTC
  • You can install Doxygen at home by following my instructions
  • Running Doxygen produces a folder named "html"
  • Inside the folder, we open the index.html file in a Web browser

Further Information

Check Yourself

  1. Of the following, an acceptable curly-brace placement and statement indentation style for function definitions is ________.
    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: comments should be placed before function prototypes instead of function definitions.
  4. By starting block comments with ________ instead of /*, you tell Doxygen to include your comments in the hyperlinked web page documentation it produces.
  5. True or false: the first line of a function block comment explains the idea of the function, not the implementation.
  6. For every function parameter, the function comment block must include the tag ________.
  7. If the function returns a value, the function comment block must include the tag ________.

8.2.4: Tracing Code

  • One critical programming skill is tracing program code, often statement by statement
  • In a sense, we have to "be the computer"

    Be the computer

  • To trace the code, we follow the flow of execution
  • Every program starts with the first line of the main() function and continues sequentially statement by statement from there
  • A function call transfers the flow to the first statement of the called function
  • When the function returns, the flow transfers back to the point from which the call was made
  • Thus, every time the flow of control reaches a function call, the program:
    1. Temporarily stops executing in the current function
    2. Jumps to the called function and executes the statements of that function
    3. Returns to the point in the code from which it jumped
  • Let us trace the flow in the following code by line number:

Example Program for Tracing the Flow

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
#include <iostream>
using namespace std;

// Returns the square of a number
double square(double number);

// Displays a message to the console
void log(string funName, double value);

int main() {
    double number = 5;
    log("main", number);
    double result = square(5);
    log("main", result);

    return 0;
}

double square(double number) {
    log("square", number);
    double result = number * number;
    log("square", result);
    return result;
}

void log(string funName, double value) {
    cout << "In " << funName << "() the value is ";
    cout << value << endl;
}

Check Yourself

  1. True or false: when tracing the flow of execution, we often keep track of our position by using line numbers.
  2. We start tracing from the function named ________.
  3. When the flow reaches a function call, it
    1. Temporarily ________ executing the current function
    2. Jumps to the ________ function and executes its statements
    3. When finished executing, ________ to the point from which the flow jumped
  4. True or false: when the flow calls a library function, we can look up the function to see what it does but we do not track the line numbers of the library function.

Exercise 8.2: Tracing Code through Functions (7m)

In this exercise we trace the flow of control in a program where functions call other functions, both void and non-void functions.

Specifications

  1. Create a text file named trace.txt.
  2. In the trace.txt file, list the line numbers of each statement of the program shown below in the order the lines are executed. For example, the following are the first few line numbers of the program flow (assuming main() starts on line 20):
    20, 21, 22, 36, 37, ...
    

    Do not bother to list blank lines or lines containing only the closing curly brace (}) of a function definition. For more information on tracing, see section 8.2.4: Tracing Code.

  3. Review your trace.txt file with another student to verify it is correct.
  4. Save the trace.txt file to submit to Canvas as part of assignment 8.

Program to Trace

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
#include <iostream>
using namespace std;

/**
    Returns the square of a number

    @param number The number to square
    @return the square of number
*/
double square(double number);

/**
    Displays a message to the console

    @param funName The name of the function
    @param value The value to display
*/
void log(string funName, double value);

int main() {
    double number = 5;
    log("main", number);
    double result = square(5);
    log("main", result);

    return 0;
}

double square(double number) {
    log("square", number);
    double result = number * number;
    log("square", result);
    return result;
}

void log(string funName, double value) {
    cout << "In " << funName << "() the value is ";
    cout << value << endl;
}

As time permits, review the summary below and be prepared to answer the Check Yourself questions.

8.2.5: Summary

  • Functions can call other functions as often as needed
  • It is possible for function to call functions that in turn call the original function
  • This can lead to a compiling problem
  • To avoid this problem, C++ allows you to declare functions without defining them
  • Function declarations (prototypes) have the function heading without the function body
  • The general syntax for declaring a function is:
    returnType functionName(parameter1, ..., parametern);
    
  • For example:
    double square(double number);
  • Prototypes end with a semicolon (;) rather than curly braces
  • By declaring prototypes before main() you can define the functions after main()

Function Style

  • Functions have style requirements we must follow
  • For instance, we must have a block comment before the name
  • Functions have naming conventions similar to variable names: camel case or underbars
  • When making up a name for a function, use verbs since functions perform an action
  • We indent functions statements inside curly braces, like elsewhere in our code

Check Yourself

Answer these questions to check your understanding. You can find more information by following the links after the question.

  1. How many times can functions call other functions? (8.2.1)
  2. What are the reasons for using function prototypes? (8.2.2)
  3. How does a function prototype differ from a function definition? (8.2.2)
  4. What are the four pieces of a block comment for functions? (8.2.3)
  5. What is meant by the term, "tracing code"? (8.2.4)

8.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

8.3.1: 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 caller's value when the function returns

Passing Arguments to Function Parameters

Passing arguments in a function call

How Value Parameters Work

  • During the function call, the 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?

Example of Value Parameters

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

int mystery(int param) {
    cout << "param=" << param << endl;
    param = param * 2;
    return 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;
}

Check Yourself

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

8.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
  • 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
#include <iostream>
using namespace std;

int mystery(int& param) {
    cout << "param=" << param << endl;
    param = param * 2;
    return 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;
}

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 constants

Comparing Call-by-Reference vs Call-by-Value

  • Call-by-value is like calling on a phone to get something done
  • Call-by-reference is like going somewhere to get something done
  • When you return you can bring something back like a cup of coffee
  • The following animation shows the effect of call-by-reference versus call-by-value
  • Think of the coffee in the cup as the data in a variable
  • Call-by-reference goes to the function with the cup and brings back the cup of coffee
  • Call-by-value is like making a phone call asking someone to fill a cup with coffee
  • As the coffee cup gets filled in fillCup(), the contents of the cup in the calling function is different
call-by-reference vs. call-by-value
Image source: penjee.com

Another Call-by-Reference versus Call-by-Value Analogy

  • Here is a URL to a web page: www.edparrish.net
  • If I tell you the URL, I'm passing by reference
  • You can use that URL to see the same web page I can see
  • If that page is changed, we both see the changes
  • If you delete the URL, you are destroying your reference to that page and not the actual page itself
  • If I print out the page and give you the printout, I'm passing by value
  • Your page is a disconnected copy of the original
  • You will not see any subsequent changes made
  • Also, any changes you make, like scribbling on your printout, will not show up on the original page
  • If you destroy the printout, you have actually destroyed your copy of the object
  • The original web page remains intact

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 (like 42) to a call-by-reference parameter.
  4. A reference parameter must refer to a ________ in a function call?
    1. constant
    2. expression
    3. return statement
    4. variable

8.3.3: 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 = 1, arg2 = 2;
    double arg3 = 3.14;
    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
  • Another way to call the function is:
    mixedCall(arg1, 2, arg3);
    

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 them
  4. In the previous question, we can tell which argument is call by reference because of the ________.

8.3.4: 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
  • 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
  • Usually, the best practice is to pass a large object, like a String, by reference
  • Otherwise, we should use value parameters unless a function needs to modify a parameter

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 8.3: Exploring Call by Reference (3m)

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

Specifications

  1. Copy the following program into a text editor and save it as swap.cpp:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    #include <iostream>
    using namespace std;
    
    void swap(int var1, int var2);
    
    int main() {
        int num1 = 0, num2 = 0;
        cout << "Enter two integers: ";
        cin >> num1 >> num2;
    
        swap(num1, num2);
    
        cout << "After calling function:  "
             << num1 << " " << num2 << endl;
        return 0;
    }
    
    void swap(int var1, int var2) {
        int temp = var1;
        var1 = var2;
        var2 = temp;
    }
    
  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: 8.3.1: Parameter Passing and Value Parameters.

  3. Change your program by adding the four ampersands (&) circled below:

    Program using pass by reference

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

  4. 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: 8.3.2: Using Reference Parameters.

  5. Submit your final program source code to Canvas as part of assignment 8.

When finished, please help those around you.

8.3.5: Summary

  • C++ supports 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

8.4: Designing with Functions

Learner Outcomes

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

  • Use stepwise refinement to create algorithms
  • Write functions following the black-box analogy

8.4.1: Computational Problem Solving

  • Programming is about developing a list of instructions for a computer to follow in solving a problem
  • We could try to enter our instructions to the computer first thing
  • However, software engineers have learned from experience that it is often better to do some preparatory work first
  • In general, we should develop programs in two phases as shown below

Program development flow

Algorithms

  • Notice that the output of the problem-solving phase is an algorithm
  • An algorithm is a sequence of precise instructions on how to accomplish a task
  • We use algorithms in our daily lives like:
    • Shampooing hair: lather, rinse, repeat
    • Calling someone on the phone
    • The easiest or most efficient way to get to school
    • Recipes for cooking
    • Many math operations such as adding numbers with digits in multiple columns
  • A large part of this course is developing algorithms for computers
  • We can use functions to help us develop algorithms
  • Functions take input data (numbers, words, other data) and return an output

    input → function → output

  • Algorithms are the instructions or steps to get from the inputs to the output
  • Since programs follow the same input→computation→output sequence as functions, we use algorithms at multiple levels

Check Yourself

  1. A sequence of precise instructions to accomplish a task is called a(n) ________.
  2. The two phases of program design are the problem solving phase followed by the ________ phase.
  3. The output of the problem solving phase is a(n) ________.
  4. Why is the problem-solving phase important?

More information

^ top

8.4.2: Stepwise Refinement

  • As programs get larger, they become harder to design
  • In this course, we are starting to develop more complex programs
  • If we look at a particular problem, it may seem impossible to solve because of the complexity
  • For example, the design of the following programs are not immediately obvious like:
    • a program to do your taxes
    • writing the numbers on a check as words (see textbook page 203)
    • a program to produce a barcode
    • a video game like 2048
    • a program to calculate the most economical pizza to buy
  • Before we start working on a program like these, we need a plan
  • One commonly used and powerful technique is called stepwise refinement (also called top-down design or functional decomposition )
  • We use stepwise refinement by first describing the general functions of a program
  • When a task is complex or difficult, we break it down into simpler subtasks
  • We keep breaking down the subtasks until we are left with tasks we know how to solve
  • Here is a diagram of the process:

    task breakdown

  • In algorithmic form:
    1. From a high level view, list the steps for solving the problem
    2. For each step that is too complex to implement, break it down into subtasks
    3. Repeat step 2 until all tasks are simple enough to solve easily
    4. Review, simplify and test the design
  • We will look at an example of top-down design in the following sections

Check Yourself

  1. True or false: every computer program is easy to develop.
  2. Breaking down a large problem into smaller problems is known as top-down design, functional decomposition, or ________.
  3. You stop decomposing a problem when ________.
    1. you are tired
    2. you know how to solve each task
    3. you are about three levels down from the top
    4. you feel ready to write some code

8.4.3: Case Study: Buying Pizza

  • As an example of how top-down design works, we will consider the complex problem of buying the most economical pizza

    Adapted from: Problem Solving with C++, 6/e, Walter Savitch, Addison Wesley, ISBN 0-321-41269-9, pp.209-215

  • Often times the large "economy" size of a product is not always a better buy than the smaller size
  • This is sometimes true when buying pizza
  • Pizza sizes are given in diameters like:
    • Large 14"
    • Medium 12"
    • Small 10"
    • Personal 6"
  • However, the amount of pizza we get is really the area of the pizza Click to show area
  • The problem is: which diameter of a pizza gives the lowest cost per square inch?
  • We need to write a program that gives us the price per area of any two pizza sizes

Problem Decomposition

  • To start with we ask: "What is a good single-line description of the project?"

    What is the cheapest pizza between two sizes?

  • We need to break down our problem into simpler problems
  • One technique that helps is to ask questions like:
    1. What output is expected? Click to show answer
    2. What data do we have to work with? Click to show answer
    3. What calculations must be performed? Click to show answer
    4. What input do we need and where do we get the input?
  • Other questions naturally arise as we think about the problem

Try It: Problem Decomposition (5m)

  1. Form a group of 3-4 students.
  2. Get one sheet of paper and write the names of all group members on the paper.
  3. As a group, decompose the problem of determining the best buy comparing TWO sizes of pizza, a larger size and a smaller size, into five or six general steps.

    Describe the steps in natural language or psuedocode. Avoid C++ commands at this time.

  4. Arrange the decomposed pieces in an initial order.
  5. Write the group solution on the group's sheet of paper.

8.4.4: Improving the Design

  • We use decomposition to list the initial steps or tasks
  • One such list of steps is: Click to show example
  • Is any step too complex to write the code for easily?
  • If so then we break the step down into subtasks

Simplifying the Design

  • When the breakdown is complete, we look for ways to simplify the design using pattern recognition
  • We notice that tasks 2 and 3 are very similar
  • Both of these tasks take the same type of input and return the same type of result like:
    radius = diameter / 2
    area = PI * radius2
    pricePerSquareInch = price / area
    
  • Thus we should be able to combine the steps to prevent duplicate code

Abstraction and Generalization

  • Abstraction is the reduction of a concept or idea to the most simple or basic of shared characteristics
  • For example, we calculate the size of a pizza using the area of a circle
  • Most pizzas have some irregulaties but we are able to ignore small differences when arriving at a soluton
  • Generalization is about developing a general solution that works for a range of similar problems
  • For our application, we want a solution that works for any size of pizza
  • If we know the diameter and price, we can computer the cost
  • Whenever two or more subtasks make the same computation, we should make a general function for them
  • We use abstraction to determine what essential data the function needs
  • We can make a generalization about creating functions:

    Rule of thumb: If we reuse the same sequence of 3 or more statements in 2 or more parts of our program, we should write a function to perform the task.

Try It: Create the Algorithm for a Function (4m)

  1. Use abstraction to create an algorithm for a general function to calculate the unit price given a pizza diameter and price.
  2. On the group's sheet of paper from the last Try It, list the computations the function must perform in order (see above or generate your own list).
  3. Clearly identify the parameters the function needs.
  4. Clearly identify the value the function returns.
  5. Devise a descriptive name for the function.

8.4.5: Functions as Black Boxes

  • Whenever we develop a function, it is important to design it as a black box

    Black box: a technical term for a device, system or object viewed in terms of its input and output without knowledge of its internal workings.

  • Here is a diagram of the concept:

    Black box

  • A black box is something that we know how to use but not how it operates
  • An example might be a cell phone or an iPod
  • These devices have controls that we know how to operate
  • However, we really do not know their internal workings

Designing Functions

  • A function has a specific job to do and should be designed like a black box
  • For our example, the purpose of the function is to calculate the price per square inch of a pizza
  • A programmer using the function needs to know what the function does and how to call it
  • However, a programmer should not need to know how the function does its job
  • This is known as, "treating the function like a black box"
    • Calling something a black box is a figure of speech
    • It is intended to convey the image of a physical device that we know how to use but not details of how it operates
  • If a function is well designed, then a programmer can use it as if it were a black box
  • A programmer should not have to look at the body of a function to know what the function does
  • All a programmer needs to know is what arguments are passed to the function and what value will be returned
  • When writing a function, we start with the function prototype
  • For instance, here is a possible prototype for our function:
    double calcUnitPrice(int diameter, double price);
    
  • Notice the information our function prototype (header) provides
  • A programmer knows what arguments must be sent to the function and the type of value the function returns without needing to see the function code
  • However, a programmer may need more information to fully utilize the function

Function Comment Blocks

  • An important technique for providing information about a function is the function comment block
  • A function comment block is a description of:
    • What the function does
    • The purpose of each parameter
    • What the function returns
  • For example, here is a possible comment block for our function:
    /**
        Calculates the price per square inch of a pizza
    
        @param diameter The diameter of the pizza in inches.
        @param price The price of the pizza in dollars.
        @return The price per square inch of a pizza.
     */
    double calcUnitPrice(int diameter, double price);
    
  • A function prototype with a comment block should tell the programmer all the information needed to use the function
  • However, the function comment block should not describe how the function does its task
  • The format of the function comment block is shown in the How-To's for Documenting and Organizing C++ Code
  • Every function must have a comment block before the function declaration
  • The comments, along with the prototype, allow a programmer to use the function
  • With this information, there is no need to study the statements inside the function
  • This allows the programmer to keep fewer details in mind while solving a problem

Try It: Create the Function Prototype and Comment Block (4m)

  1. For the algorithm developed in the last Try It, write a function prototype and comment block on the group's sheet of paper.
  2. State the purpose of the function, but not how it operates.
  3. Clearly specify the parameters (@param) of the function and describe them in the comment block.
  4. Clearly identify the value the function returns (@return) in the comment block.

8.4.6: Testing a Design

  • We now have the design for our program like: Click to show design
  • We may have developed other function headers as well
  • Before translating our design into code, we should test it or do a walkthrough (a.k.a. desktop testing)

    walkthrough a process of checking an algorithm step-by-step

  • For out walkthrough, we use example numbers to test our algorithm
  • If the walkthrough yields the correct result, we have confidence our algorithm is correct

Try It: Walkthrough an Algorithm (5m)

  1. Have one group member play the role of computer.
  2. For the algorithm from the last Try It, have the student "computer" try the algorithm with example data provided by the rest of the group.
  3. Correct any errors in design your walkthrough discovered.
  4. Repeat with other student's in the group playing the role of the computer.
  5. Write down the result of each students price-per-square-inch compuputation and decision made on the best buy.

8.4.7: Translating into C++

  • Whenever you write software, we always start with something that we know compiles and runs
  • As an example, we start with the following code that we know compiles and runs
1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int main() {
    // Enter your code here

    return 0;
}
  • We save the file as pizza.cpp, and then compile and run it to make certain everything works so far
  • We add to the program a few lines at a time, and then compile and run the code to test the code we added
  • If the added code does not compile or run, we know where the problem exists
  • If we find an error, we correct it before continuing

Creating Stubs

  • When implementing our code incrementally, sometimes it makes sense to put all the functions in place
  • This lets us verify that the overall structure is correct
  • However, we do not want to actually write the statements for the function at this time
  • Instead, we can "stub out" the function definition and then compile and run the program to make sure everything still works

    Stub: a minimal set of code to act as a placeholder for a function

  • Function stubs are like an outline of a function but without the algorithm implemented
  • When a function must return a value, we return a "dummy" value
  • For example:
    double calcUnitPrice (int diameter, double price) {
        return 1; // dummy value
    }
    
  • Stubs allows us to design and code a high-level structure without worrying about the details

Coding calcUnitPrice

  • We translate our psuedocode into C++ and obtain the following:
    double calcUnitPrice(int diameter, double price) {
        const double PI = 3.14159623;
        double radius, area;
        radius = diameter / 2;
        area = PI * radius * radius;
        return (price / area);
    }
    
  • What do you notice about the code?
  • The following statement looks right but is a serious mistake
    radius = diameter / 2;
  • We want the radius to include the fractional part of the size
  • However, integer division truncates the remainder
  • How do we fix the problem?
    double calcUnitPrice(int diameter, double price) {
        const double PI = 3.14159623;
        double radius, area;
        radius = diameter / static_cast<double>(2);
        area = PI * radius * radius;
        return (price / area);
    }
    
  • Now radius will include fractional parts
    • This would work as well:
      radius = diameter / 2.0;
  • Here is a link to a completed program: pizza.cpp

Testing

  • Just because a program compiles and runs does not mean that it is correct
  • To verify that you program returns the correct answers, you should test it with known values and compare it with known results
  • You can get known values and results by using pencil and paper or a calculator
  • Once you have checked your program with a few known values, then you can have more confidence that it is correct

Check Yourself

  1. A function with just enough code to allow the function to compile is known as a ________.
  2. True or false: we add function stubs to our code as a placeholder until we write the function body.
  3. True or false: one should compile and test code after every few lines are added so that one knows where the problem occurs.

Exercise 8.4: Translating an Algorithm into Source Code (5m)

In this exercise we complete our pizza buying program for deciding between two sizes of pizza.

Specifications

  1. Copy the following program into a text editor, save it as pizza.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. Take the tested algorithm from the last Try It and translate it to C++ code, keeping the algorithm steps as comments in your program.
  3. Test your code with the values used in the walkthrough at a minimum.
  4. Submit your final program source code to Canvas as part of assignment 8.

When finished, please help those around you. As time permits, review the following summary and be prepared to answer the Check Yourself questions.

8.4.8: Summary

  • We write functions in our code for these reasons:
    1. To organize our code into modules
    2. To reduce repeated code
    3. To allow for code reuse in another program
  • To develop a program around functions we use stepwise refinement (aka top-down design or functional decomposition)
  • With stepwise refinement we create our programs or algorithms by
    1. First describing general functions
    2. Breaking each function task into smaller functions as needed
    3. Continuing to refine in successive steps until the whole program is fully defined
  • Eventually, the tasks are so trivial that they are easy to code
  • We applied top-down design to the problem of buying the best size of a pizza
  • Along the way, we made sure to design our functions as black boxes
  • A black box refers to something that we know how to use, but not how it operates
  • A programmer who uses a function needs to know what the function does, not how it is coded
  • We want to write functions so that the function declaration and block comments are all a programmer needs to call the function

Check Yourself

  1. What are the two main phases of program development? (8.4.1)
  2. What is the output of the problem-solving phase? (8.4.1)
  3. What is meant by the term top-down design? (8.4.2)
  4. When should you stop decomposing (breaking down) your design? (8.4.2)
  5. If you notice repeated code in your design or program, what should you do? (8.4.3)
  6. What does it mean to say that a programmer should be able to treat a function as a black box? (8.4.4)
  7. What is the purpose of the comment that accompanies a function declaration? (8.4.4)
  8. What is one way to test a design? (8.4.5)
  9. What is a function stub? (8.4.6)
  10. If a stub must return a value, what value should it return? (8.4.6)
  11. What is the purpose of testing code that compiles? (8.4.6)

Wrap Up

Due Next:
A7-Programs With Functions (10/19/17)
A8-Multi-Function Programs (10/26/17)
  • When class is over, please shut down your computer
  • You may complete unfinished exercises at the end of the class or at any time before the next class.
Last Updated: October 18 2017 @23:55:45