Contents

An abstract introduction to functions

We use the term function in programming in pretty much the same way Mathematicians use it. A function is a way of encapsulating some set of operations over a set of inputs and either produces nothing or an output. We refer to the inputs as parameters and the output is the return value. For example, the mathematical function sqrt(x) takes one parameter, x, which is a real number, and then returns the square root of that number.

In programming, we take this same basic idea, only we can create arbitrary functions—you can do that in math, too, but my guess is you've never done that :). So why do we use functions? There are a lot of reasons, actually. Let's start by considering this example. If I wrote code to take the square root of a number, and didn't use a function, then every time I wanted to take a square root, I'd have to write all of those steps. This violates a couple tenants of programming, the first of which is that redundancy (e.g., the exact same code repeated over and over again) is bad. Second, cognitively it's much harder to look at a set of steps and comprehend what's going on. So using functions allows your code to be more concise, readable, and understandable.

(Back to top)

Elements of a C++ function

In C++, functions show up in three places: declarations, definitions, and invocations. Functions should be declared above main and defined below. The reason for the former is that functions must be declared before they can be invoked. We put the definitions below in this class just as a convention—in future classes or your place of work, you may find that this is not the case. Invocations can occur in any function, including main.

Signature

Functions are described by their signature, which consists of the return type, name, and parameters. Here's an the signature for main:

Here's the signature of a function named printName that doesn't return anything, but takes a string as a parameter:

For each parameter, the signature must include the type and a name that will be used to refer to that parameter within the function. The order matters—whatever order you put the parameters in in your signature, you must use the same order in your declaration, definition, and invocation.

Declaration

Declarations consist of the signature followed by a ;. Technically, the parameter names do not have to be present—the purpose of the declaration is to let the compiler know that the a function with this name, return type, and parameter types will be defined at some point later in the program. So, both of the following are correct declarations.

With parameter names.
Without parameter names.

In this class, we will follow the first style, and include the parameter names.

Definition

Definitions consist of the signature followed by a pair of curly braces. If the signature includes a return type, then the function should always return something of that type, using the return statement. Here's an example.

Unlike function declarations, you need to include the parameter names in a function definition. The comment above is what we call a JavaDoc comment. It tells us what the function does, what the parameters are, and what's returned, if anything (there's a @return annotation for return values). Be sure to take a look at the style guide for information about the format of these comments.

Invocation

Invocations consist of the function name, a pair of parentheses, and then include the variables or values you want to correspond to each parameter from the signature, in the same order. The names of the variables you pass in do not need to be the same as the parameter names!!! Here's and example of invoking our function from main:

It's crucial to understand what happens when you invoke a function in terms of memory. The function in which an invocation is made, e.g., main in the example above, is called the caller. New memory is allocated for the function to do its thing. This new memory include space for all of the parameters and variables declared within the function. The values passed from the caller to the function are copied over to this memory, and are stored under the parameter names. The program control is then handed over to the functions and the function executes. Once the function has finished or returns a value, program control is given back to the caller, along with the return value if anything is returned. Once the function has returned control, it and all of its variables (including the parameters) are removed from memory. One consequence of this is that function instances are complete independent of each other—if I invoke printName multiple times, each invocation has no knowledge or shared variables with any of the other invocations.

Putting it all together

Here's a full example of declaring, defining, and invoking a function in a C++ program:

Here is a play-by-play description of how memory is used during function invocations. First, we will start off with a slightly different version of the program above. Here, we are storing the value "Hank" in a variable named myName in main before invoking printName with that variable as a parameter.

Start

The initial program.

Step 1

The main function is invoked by the system; space is made for all of main's variables and we keep track of the return value (an int).

Step 2

The string "Hank" is stored in the variable myName inside of main's memory.

Step 3

The printName function is invoked. When this happens, new memory is created for the function and an entry is made for every parameter and variable declared within the function. The function does not return anything, so we do not need to worry about keeping track of a return value. The values of any variables or literal values passed in a parameters to a function during invocation are then copied to the memory reserved for those parameters in the function's memory space. In this case, the variable myName in main was passed as a parameter. Therefore, we take the value of that variable, "Hank", and then copy that value to the space for the corresponding parameter in the function signature, which is name.

Step 4

The program control now enters the actual function (line 14).

That line causes the following to be output:

Name: Hank

Step 5

Line 14 was the final line of the function, so after its execution, program control goes back to the calling function, i.e., main. Note that all of the function's memory is removed.

Step 6

The next line in main is executed, which is a return statement. We will store this value in the "return value" section of main's memory block and this will tell us what value to give to main's caller (i.e., the system).

Finished

The program has finished execution.

(Back to top)

Pass-by-value

In the functions above, we're passing parameters by value. That means that the values of variables passed in when a function is invoked are copied into memory designated explicitly for the function. That also means that if you reassign a new value to a parameter, that reassignment will not be reflected outside of the function. Here's an example:

This will print:

Hank
Hank
(Back to top)

Pass-by-reference

Often times, you'd like to have a function reassign the value of a parameters. In order to do this, you need to pass by reference. This involves adding an ampersand (&) prior to parameter names in the function signature (so your declaration and definition will be affected). You don't need to do anything special in the function. Here's the code from above, with pass by reference:

This will print:

Hank
Bob

Perfect! Now, lets suppose that you don't want to allow changes to the data that's passed in. That may seem funny, since we motivated using pass-by-reference so that we could change the underlying value. But there are other motivations, such as not wasting memory by uselessly copying structures over and over again. So, if you want to not copy the data, but you also don't want to be able to change the value, then there is a special keyword that precedes the parameter type in the signature: const. If you try to alter the value of a parameter that is const, then the compiler will get mad at you. Here's an example:

Here is the memory diagram for an adaptation of the program above. This only shows the step when the resetName function is invoked and memory is created for the function instance. We can see that the function's name parameter is directly linked to the value of the variable that was passed in (i.g., myName).

(Back to top)