Arrays, bounds, and logical size

Computer Science I

0: active; 1: active; 2: active; 3: spare.
Original learning diagram: 0: active → 1: active → 2: active → 3: spare.

An array stores several elements of one type together. An index selects a position in that storage. The index is not the element's value: the value at position zero might be six, while the position itself is still zero.

A fixed amount of storage does not mean every slot contains an observation we want to process. We will keep the available capacity separate from the number of accepted values.

Count the positions before indexing #

int scores[5]{}; creates five integers, all initialized to zero. Their positions are zero, one, two, three, and four. There is no position five. The number of elements is five, while the last valid index is one less.[1]

Built-in array subscripting does not automatically check bounds. An out-of-range access has undefined behavior, even if a particular run seems to produce a plausible number. The program must establish that an index is valid before using it.

Here is a complete example with five available slots and three incoming observations.

#include <iostream>

int main() {
    constexpr int capacity = 5;
    int scores[capacity]{};
    int used = 0;
    const int incoming[] = {6, 8, 10};
    for (int value : incoming) {
        if (used < capacity) {
            scores[used] = value;
            ++used;
        }
    }
    int total = 0;
    for (int i = 0; i < used; ++i) {
        total += scores[i];
    }
    std::cout << used << ' ' << total << '\n';
}

The declaration of capacity gives a compile-time constant of five. The array uses that extent to create five zero-initialized elements. Used starts at zero, meaning that none of those slots yet holds an accepted observation. Incoming contains the three values six, eight, and ten; its size follows from that initializer.

Follow each append in order #

The range loop takes each incoming value in turn. Its variable value receives a copy of the current element. Changing that variable would change the copy, not the incoming array.

For six, the condition checks whether used, currently zero, is below capacity five. It is. The assignment stores six in scores at index zero. Only after storing does the program increase used to one.

For eight, used is one. There is still room, so eight goes into index one and used becomes two. For ten, used is two. Ten goes into index two and used becomes three.

At this point the stored elements are six, eight, ten, zero, and zero. The active observations are only the first three. The initialized zeros in the remaining slots are unused storage, not additional observations.

The capacity check comes before the assignment. If used were already five, the condition would fail and the body would skip that candidate. This example's policy is to ignore excess incoming values. A user-facing program might instead report a full collection; it should make the policy explicit.

Process only the accepted values #

After the input loop, total starts at zero. The index loop begins at zero and continues while the index is below used. It therefore visits indices zero, one, and two.

The first addition makes total six. The second makes it fourteen. The third makes it twenty-four. When the index becomes three, the condition fails and no unused slot is read. The final output displays used and total as 3 24, followed by a newline.

A range loop over incoming is appropriate because all three of its elements are inputs. A range loop over scores would visit the entire capacity of five. It would not automatically stop after the three active observations.

Keep one rule true across operations #

The invariant is 0 <= used && used <= capacity. It states that the active count cannot be negative or exceed storage. Active elements occupy [0, used), a range that includes zero and excludes used itself.

Appending preserves this rule when we first check for space, store at used, and then increase used. If the candidate comes from user input, validate it before both the storage and count updates. Rejected values must not consume slots. A sentinel that means stop must not become an observation.

A mean uses the active count as its denominator. For these three values, twenty-four divided by three gives eight. Dividing by capacity instead would give 4.8, incorrectly treating the unused zeros as data. When used is positive, static_cast<double>(total) / used keeps fractional results; with used zero, report that no mean exists before dividing.

Moving values without losing them #

To insert a value into the middle, first check for spare capacity and a permitted insertion position. Then move existing elements right, starting at the last active element. Moving from the end prevents a destination from overwriting a value that has not yet been moved.

For active values six, eight, and ten, inserting seven at position one requires moving ten to position three and eight to position two. Then store seven at position one and increase used to four. Moving eight first in the wrong direction could overwrite ten before preserving it.

To remove the element at position one from that result, move following elements left, then decrease used. The active values become six, eight, and ten again. The old contents beyond the new active end do not count as observations. Neither insertion nor removal changes the fixed capacity.

Passing and reversing an active range #

A parameter written const int values[] lets a function read array elements without modifying them through that parameter. It does not carry the original built-in array's extent, so pass an active count too. The caller must ensure that count fits the actual storage. Without const, the function can deliberately change the original elements.

For a small signed count, reverse traversal can begin at used - 1 and continue while the index is nonnegative. At used zero, the initial index is negative and no access occurs. Unsigned counters need a different pattern because subtracting one from zero wraps rather than becoming negative. The condition must prove the index safe before every access.

Practice with explained answers #

With capacity eight and used three, active indices are zero, one, and two. The next append position is three because that is the first unused slot. Capacity eight provides room, but it does not make indices three through seven active yet.

If a search returns -1 to mean absent, check that result before indexing. It is a status value, not an acceptable array position. These distinctions prepare CS2 Lectures 3 and 4 and the stored observations and inventory operations in Labs 2 and 3.

References

  1. ↑ C++ working draft: arrays .