CIT020 Index > Lecture Notes - Functions

Functions

What’s a Function?

A function is a part of a program that does a specific task. Functions have two main purposes:

  1. To avoid duplication. If a program has to do a particular task many times, you can put its code into a function and then simply call that function rather than copy and paste the code.
  2. Modularization. Rather than having one huge, long program, you can use functions to break the program into subtasks. This makes the entire program easier to read, as you don’t need to hold everything in your head at once.

You can think of a function as if it were a recipe. For example, a cookbook may have a recipe for Eggs Benedict, which needs Hollandaise sauce. It might look like this:

  1. Slice an English muffin.
  2. Begin boiling water.
  3. Make Hollandaise sauce as described on page 211.
  4. Poach an egg when water boils.
  5. Put egg on muffin, and slather with Hollandaise sauce.
  6. Eat.

At the end of step 2, you put aside the eggs and muffin, and turn to page 211. After you’re done with that recipe, you return to step 4 and continue with the recipe. This approach also is modular. The recipe for Salmon and Hollandaise sauce won’t have to repeat the part about the sauce; it can just point you to page 211 and off you go.

A function has this generic form:

type name( parameters ) // function header
{
	function body
}
type
This tells what kind of value the function returns to you. For example, the square root function returns a double and the toupper() function returns a char. If a function doesn’t return a value, you use the word void here.
name
The name of the function follows the same rules that apply to a variable name (must begin with letter or underscore, followed by letters, digits, or underscore). In this class, all the functions you define will have names that are verbs, because functions do things. Thus, an acceptable function name would be getMagicObject, but not magicObject.
parameters
This is the “extra information” that the function needs to accomplish its task. For example, I can’t just tell you “Find the square root.” You would immediately ask, “of what?” That information that you need comes through the parameters. toupper() needs a character as its input– which character are you converting to uppercase? srand() needs an integer as its input–where would you like the random number generator to start? rand(), on the other hand, doesn’t need any extra information from you. It just generates a random number all by itself
function body
This is how the function does its work.

Let’s give some examples.

Void functions with no parameters

When you call a function to show a game’s instructions, that function doesn’t require any extra information, and it doesn’t return some numeric or character value to you. That means you have a void function with no parameters:

void showInstructions( )
{
    cout << "Move your character through the maze." << endl;
    cout << "Press J to jump, L for left, R for right," << endl;
    cout << "U for up, and D for down.  Q quits the game." << endl;
}

You would call this function from main( ) as shown in the following code. Notice that, even though there are no parameters, you must put parentheses after the function name. That lets the compiler know you are calling a function rather than referring to a simple variable.

int main( )
{
    const int N_ROWS = 10;
    const int N_COLS = 10;
    int maze[N_ROWS][N_COLS];
    
    showInstructions( );
}

Prototypes

If you try writing this program and put the showInstructions() function after main(), it won’t work. The compiler needs to know what showInstructions is, just as it needs to know your variable names before you use them. You have two choices. You can either put the function before main() in your .cpp file, or you can give the compiler a “preview of coming attractions” by putting a copy of the function header before main(). This copy is called a function prototype, and it makes the compiler happy. Note that the prototype ends with a semicolon, the header never does.

void showInstructions( );

int main( )
{
    const int N_ROWS = 10;
    const int N_COLS = 10;
    int maze[N_ROWS][N_COLS];
    
    showInstructions( );
}

Void functions with parameters

Some functions just display information and don’t calculate any values to return to you, but they do need extra information to do their job. For example, here is a function that displays the number of lives left, pluralized correctly. To do its job, you have to tell it how many lives to display. In the following code, numbers in black boxes like this:  0  refer to the notes that are after the program.

void showLives( int nLives ); // function prototype

int main( )
{
	int nLives = 9;  1 
    
    // ...
    showLives( nLives );	 2 
    // ...
	return 0;
}

void showLives( int nLives )  3 
{
    cout << "You have " << nLives;
    if (nLives == 1)
    {
        cout << " life ";
    }
    else
    {
        cout << " lives ";
    }
    cout << "remaining." << endl;
}  4 

 1  When the progra encounters this line, the variable nLives is created in the main() function.

 2  This line is the function call. The main() function goes “on hold,” and the showLives() function becomes active. The value in the parentheses is called the argument, and it will be passed on to the function.

Diagram showing copy of parameter value  3  The showLives() function has one parameter, which is a placeholder variable, a “fill-in-the-blank” for information from the caller. In this case, the placeholder variable is also called nLives, and it receives a copy of the actual parameter.

 4  When the function finishes, the nLives parameter goes away (it is out of scope), and the main() function comes off “hold” and continues.

Functions that return values, but have no parameters

These aren’t very common. The only good example that I can give you is the rand() function. As mentioned before, you don’t need to give it any information, but it returns an integer to you.

Functions that have parameters and return values

These are by far the most common functions. For this example, we will have a function that takes two parameters: an object’s mass and its velocity, and returns the kinetic energy of that object (a measure of how much power it has, so to speak).

double calcEnergy( double mass, double velocity );  // prototype

int main( )
{
    double mass = 0.0;
    double velocity = 0.0;
    double kineticEnergy = 0.0;  1 
    
    cout << "Enter mass in kg: ";
    cin >> mass;
    cout << "Enter velocity in m/sec: ";
    cin >> velocity;    2 
    kineticEnergy = calcEnergy( mass, velocity );   3   7 
    cout << "The kinetic energy is " << kineticEnergy << " joules." << endl;
}

double calcEnergy( double mass, double velocity )  4 
{
    double result = 0.0;  5 
    result = (mass * velocity * velocity ) / 2.0;
    return result;  6 
}
 1  After the variables are declared, this is what things look like. Notice that the variables belong to the main() function.
mass, velocity, and kineticEnergy all zero
 2  After the user input, this is what things look like.
now mass is 45.0, velocity is 13.0
 3  This is an assignment statement, so, just as before, we have to get out a piece of scratch paper and completely evaluate the right-hand side. This means that evaluation of the right-hand side goes on hold as we call the function.
 4  The function becomes active, and says, “I have two parameters (placeholders). Please fill them in. The program fills them with copies of the corresponding arguments from main(). Notice that parameters belong to the function where they are declared.
mass and velocity copied to parameters
 5  The variable result is defined. It belongs to calcEnergy() because it is inside the function body.
result variable added to calcEnergy
 6  When the program encounters the return statement:
  1. It figures out the value of the expression after the word return. In this case, it’s the value of the variable result, which is 3802.5.
  2. It copies that value to the scratch paper.
  3. It says, “My job here is done,” and all of the parameters and variables for the function go away.
result transferred to scratch paper
 7  The main program comes off hold, and discovers that the right hand side has been completely evaluated. It then follows the rule of assignment statements by taking whatever value is on the scratch paper and copying it into the variable named on the left-hand side of the equal sign (kineticEnergy).
scratch paper transferred to kineticEnergy

Why All This Trouble?

At this point, you may be wondering why we are going to all this trouble of passing parameters. The names are the same, so why are we creating duplicate copies that belong to the function? Why not use global variables to make our life easier? Why go to the trouble of returning a value to the scratch paper? Why not just change a global variable?

We could rewrite our code and make all the variables global by putting them outside both functions.

void calcEnergy( );  // prototype

double mass = 0.0;
double velocity = 0.0;
double kineticEnergy = 0.0;

int main( )
{   
    cout << "Enter mass in kg: ";
    cin >> mass;
    cout << "Enter velocity in m/sec: ";
    cin >> velocity;
    calcEnergy( );
    cout << "The kinetic energy is " << kineticEnergy << " joules." << endl;
}

void calcEnergy( )
{
    kineticEnergy = (mass * velocity * velocity ) / 2.0;
}

Although the program still works, doing this is a very bad idea!

  1. First, the code is now tied down to these precise variable names. If anyone else wants to use our code to calculate energy, they must name their variables exactly the same as ours. That’s not very flexible.
  2. Since the variables are global, it’s a free-for-all. Any function is free to change those variables, and that makes maintenance a nightmare. With parameters, the function gets a copy of the original variable. It can change the copy as much as it wants without ever harming the original.

When Globals are OK

Global variables are fundamentally evil. Global constants, on the other hand, are quite useful. If a game has many functions that all need to know the size of the playing grid, it makes sense to declare const int N_ROWS = 10; and const int N_COLS = 10; rather than having to pass those parameters to every single function. Other people can still use your code if you are careful to put these declarations into a header file, which they can use with a #include.

A Quick Note About Scope

Whenever you open a block of code with a brace {, you open a new scope. The book shows you that you can nest scopes and create duplicate-named variables that are in different scopes. The book says it’s not a good idea. That is wrong. It’s a terrible idea. Something like this is just insane:

int main()
{
    int var = 10;
    
    cout << var << endl;
    {
        int var = 20; // new scope, new variable!
        cout << var << endl;
    }
    cout << var << endl; // back to outer scope
    return 0;
}

A for loop opens a new scope, and it’s fine to declare a counter variable whose scope will be the loop body. If you nest loops, though, you should never use the same variable name in both loops!

for (int i = 0 ; i < N_ROWS; i++)
{
    for (int j = 0; j < N_COLS; j++) // This is OK; the variable names are different
    {
        grid[i][j] = 0;
    }
}