Array parameters and partially filled arrays

Computer Science II · Lecture 4 ·

A six-element array has three active values and three unused slots.
Logical length and physical capacity are separate: only the first count elements hold active data.

A partially filled array has room for more values than it currently contains. Capacity describes available storage. The active count describes meaningful data. A processing function needs that active count so it does not confuse empty room with accepted input.

Start with CS1: arrays and active counts. This lecture explains how to preserve the same distinction across input, calculation, and functions receiving the array.

Two numbers with different jobs #

Suppose an array has room for ten integers, but the program has accepted four. Capacity is ten; used is four. Active elements occupy indexes zero through three. The next free position is index four. The other allocated slots exist, but they are not part of the current dataset.

The rule 0 <= used && used <= capacity is an invariant: a relationship every operation must preserve. Used must not be negative and must not exceed storage. With a positive used count, used - 1 is the last active index. With zero used elements, there is no active element to index.

Appending takes three steps. First check that used is below capacity. Next store the accepted value at index used. Then increase the count. Increasing first would move the count away from the intended next slot. Increasing after rejected input would falsely claim that another meaningful value exists.

A sentinel marks the end of input. It is a control signal, not another observation to average. Check for it before storing the value or increasing the count. A full array also needs an explicit response, such as stopping input or reporting that another value will not fit.

Walk through the mean of active values #

For the accepted inputs two, eight, nine, and six, the count progresses from zero to four. The running sum becomes two, ten, nineteen, and twenty-five. The average is twenty-five divided by four, or 6.25.

The calculation must divide by four, not by the capacity ten. It must also avoid reading unused positions. Their current bytes do not determine whether they belong to the dataset. When no values are accepted, the count is zero and the ordinary mean is undefined; handle that situation before calling a function that requires nonempty input.

What an array parameter actually supplies #

An ordinary built-in array parameter gives a function access to the original elements. It does not copy the complete array and it does not carry its bound. The function therefore receives the active count separately. The const element type says that the function will not modify those integers through this access path.[1]

double mean(const int values[], int used) {
    // Precondition: used > 0 and values has at least used elements.
    int total = 0;
    for (int i = 0; i < used; ++i) total += values[i];
    return static_cast<double>(total) / used;
}

The call supplies two pieces of information: where the elements begin and how many active elements to visit. The comment states the requirement that used is positive and that enough elements are available. The comment documents the contract; it does not perform a runtime test.

Inside the function, total starts at zero. For each index below used, one original element contributes to the sum. The cast converts total to double before dividing, so the calculation preserves a fractional quotient. Converting only the finished integer quotient would be too late to recover the discarded fraction.

This implementation also requires every running integer sum to fit in int. The cast after accumulation cannot repair signed integer overflow that has already occurred. Its small example values satisfy that requirement.

The function returns a double and leaves the array values in their original order. That read-only behavior is a separate promise from receiving access to the caller's storage.

Changing elements versus changing a pointer #

With a non-const array parameter, assigning to an indexed element changes the caller's element, because the access refers to the original storage. Reassigning a pointer parameter is different: an ordinary pointer parameter is a local copy of an address. Changing that local address does not automatically replace the caller's array or pointer variable.

The array-processing interface must make both the length and permitted changes clear. A sorting function may rearrange active elements; a sum function usually should not. A function that changes the active count needs an explicit way to communicate that new count to the caller.

Range loops do not know your active count #

for (int value : values) copies each visited integer into a local variable. Assigning to value changes that copy. for (int& value : values) visits by reference, so changing value changes the corresponding original element.

These range-loop forms apply to an actual array or a suitable container available in the caller. The values parameter inside mean has adjusted to a pointer and cannot itself supply a range for this loop.

A range loop over a whole built-in array visits the whole array, including any unused capacity. It does not discover your separate used count. For a partially filled built-in array, an indexed loop with the active bound is often the straightforward choice. Likewise, sorting and printing must stop at the same active boundary used by averaging.

Practice and explanation #

An array has capacity six, used is two, and one more valid value arrives. Where should it be stored, and what is the new used count? Store at index two, then make used three. Do not store at index three first, and do not change capacity.

If the next input is rejected, both used and the active data remain unchanged. This preserves the promise that every counted position contains an accepted observation. If used equals capacity, the next append cannot proceed without a separate expansion strategy.

References

  1. ↑ C++ working draft: functions .