Functions, contracts, scope, and references

Computer Science I

argument; value copy; reference alias; return.
Original learning diagram: argument → value copy → reference alias → return.

A function gives a name to a task. A caller supplies inputs, the function performs its work, and control returns to the caller. This lets us separate a calculation from the place where its result is used.

We will follow two functions: one returns an area, and the other changes an existing counter. Their effects differ even though both are called using parentheses.

Read a function's interface #

An interface describes how to call the function. In double rectangle_area(double width, double height);, the first double is the return type. The name identifies the task. The two parameters describe the input types and give those inputs names inside the function. The semicolon makes this a declaration without a body.

A definition includes the body containing the task's instructions. A call such as rectangle_area(3.0, 4.0) supplies arguments. Arguments are the expressions at the call site; parameters are the names that receive their values inside the function. They need not have matching names.[1]

#include <iostream>

double rectangle_area(double width, double height) {
    // Precondition: both dimensions are nonnegative.
    return width * height;
}

void add_one(int& value) {
    ++value;
}

int main() {
    int count = 2;
    add_one(count);
    std::cout << rectangle_area(3.0, 4.0) << ' ' << count << '\n';
}

Writing the function definitions above main makes them available to calls below. Their bodies do not run simply because the definitions appear earlier in the file. Ordinary execution still begins in main.

Follow the change to count #

Main first creates count with value two. It then calls add_one, passing count as the argument. The parameter is declared with int&, so it is a reference parameter: value is another name for the caller's existing integer.

Inside add_one, increasing value changes that same integer from two to three. When the function reaches its closing brace, control returns to the statement after the call in main. Count is now three.

The return type void means add_one does not supply a result value. Its work is the deliberate change to count. Calling it is a statement in this example; there is no separate returned number to store.

Follow the area calculation #

The output statement calls rectangle_area with arguments 3.0 and 4.0. The value parameter width receives 3.0, and height receives 4.0. The body multiplies them, giving 12.0.

A return ends this function call and provides its result to the caller. The output statement uses that result, then outputs a space and the current count of three. The display is 12 3, followed by a newline.

Rectangle_area calculates and returns the area; it does not itself print it. Keeping those tasks separate lets another caller store the area, compare it with a limit, or combine it with another calculation without also printing.

Copying a value is different from sharing an object #

A value parameter has its own stored value. If rectangle_area assigned a different number to width, that change would affect its local parameter, not the caller's original variable. A reference parameter instead gives access to the same object, so writing through it changes the caller's object.

To trace a value parameter, draw a separate box containing the copied value. To trace a reference, draw one box with both names referring to it. This avoids treating every parameter as either always independent or always shared.

A const reference allows reading an object without permitting changes through that reference. The const qualification is a restriction on this access, not a promise that no other part of the program can ever change the object.

Scope controls where a name can be used #

A name is available only within its scope. The count declared inside main is local to main's body. Add_one can modify it because the caller passes a reference, not because the function can freely use the name count.

Parameters belong to the current function invocation. A later call has its own parameters. Similarly, an inner block can declare a name that hides an outer name within that block. The outer object continues to exist; declaring the inner variable does not overwrite it.

Passing needed data explicitly makes dependencies visible. A mutable global variable can be changed from several places, which makes it harder to identify what a particular call may do.

Say what callers must provide #

A function contract states the accepted inputs and expected effects. A precondition is what must be true before the call. A postcondition describes the result or allowed change after it.

The comment in rectangle_area requires nonnegative dimensions. The comment documents the requirement but does not check it. Either the caller must validate dimensions, or an expanded function must implement an explicit policy for rejecting them. The example uses 3.0 and 4.0, which satisfy that contract.

Test the area function with an ordinary rectangle and with either dimension zero. Test add_one by checking count before and after its call. A returned value and a change to caller data are separate effects, so neither test substitutes for the other.

Default arguments allow callers to omit trailing inputs for which values are specified. Overloads use the same name with different parameter types or counts. CS2 Lecture 2 develops those extensions; they still rely on the same declaration, call, and parameter model.

Practice with explained answers #

Suppose a function takes int x, sets x to nine, and returns x. The caller has int a = 2; and executes int b = f(a);. The value parameter begins with a copy of two. Its assignment changes the copy to nine, and nine is returned into b. Thus a remains two and b becomes nine.

Changing the parameter to int& x makes x an alias for a. The assignment then changes a to nine as well. This distinction prepares CS2 Lectures 1 and 2 and the later array, pointer, and vector interfaces.

References

  1. ↑ C++ working draft: functions .