Arrays, low and high
Keeping a row of values under one name, choosing the indexes, and using low and high so a loop survives a change to the size.
A row of slots with one name
An array is a fixed row of values of one type. The declaration scores: array[1..5] of integer; says that scores holds five whole numbers, reached as scores[1] through scores[5].
The size is part of the type and is fixed at compile time, so an array never grows or shrinks while the program runs. If you need a different number of slots, you change the declaration.
Reading a slot that is outside the declared range is a mistake the compiler cannot always catch. In a checked build it stops the program; in an unchecked one it quietly reads whatever is next in memory, which is worse.
You choose the indexes
Pascal does not force arrays to start at zero. array[1..5] and array[0..4] are both allowed, and so is anything else with ordered values, such as array['a'..'z'].
Rather than hard-coding a bound, ask the array. low(scores) and high(scores) return the first and last legal index, so a loop written as for i := low(scores) to high(scores) keeps working when the declaration changes.
The count of slots is one more than the difference between those bounds, which is worth writing as an expression: high(scores) - low(scores) + 1. For an array starting at zero, high alone is the count minus one, and mixing the two up is a common off-by-one.
That freedom is useful, and it is also a decision to make once and keep. A list of scores indexed from one reads naturally to a person; a list indexed from zero matches the way offsets work in memory. Either is fine, but a program that mixes the two conventions will be wrong wherever they meet.
Walking an array
The usual shape is a loop over the indexes with a running total or a comparison inside it. Nothing is summed automatically: the loop is where the work happens.
An array of numbers divides freely, but remember that total / count is a real division, so the average needs a real variable or a format when it is printed.
The loop body is also the place to decide whether an unfilled slot should count. A global integer array starts as zeros, and a zero in a list of exam scores is indistinguishable from a real score of zero. If that matters, keep a separate count of how many slots have been filled rather than trusting the array to know.
Declare, fill, walk
program Main;
var
scores: array[1..3] of integer;
i: integer;
begin
scores[1] := 10;
scores[2] := 20;
scores[3] := 30;
for i := low(scores) to high(scores) do
writeln(i, ': ', scores[i]);
end.Arrays are values, not references
An array is one value made of slots, and := copies the whole thing. After copyOf := scores;, changing copyOf[1] leaves scores[1] alone. The same rule applies when an array is passed to a routine without var: the routine gets its own copy, and any changes it makes are discarded when it returns.
That is convenient for a report function, which can sort or reverse its copy without disturbing the caller, and expensive for a large array, which is copied in full at the call. A var parameter avoids the copy and lets the routine write to the original. The compiler also knows the array type exactly, so it can lay the slots end to end and turn an index into an offset from the first one.
Off-by-one, in words
Suppose the array is declared array[1..5]. A loop from 1 to 5 touches five slots: 1, 2, 3, 4 and 5. A loop from 1 to 4 touches four and silently skips the last value, which is the bug that makes a total come out slightly low. A loop starting at 0 reaches one slot before the array exists, which a checked build refuses and an unchecked one does not.
The safe habit is to write low(scores) and high(scores) instead of numbers, even when the declaration is right in front of you. The names keep working when the bounds move, and they make the count expression explicit: high - low + 1. If you remember one thing, remember that the count is not the high bound unless the array starts at zero.
Three array mistakes to watch for: using an index below low or above high, which an unchecked build does not stop; forgetting to reset the total before the loop, so it starts from a value left by earlier work; and writing scores[i] = 10 with = instead of :=. The first is the dangerous one because the program may keep running with the wrong slot.
Try these next
- Change the declaration to
array[0..5], add a sixth value, and letlowandhighkeep the loop correct. - Copy the array into a second variable and change one slot; print both to prove they are separate.
- Reverse the loop with
downtoand check that the total is unchanged.
Five scores, one total
Run it, then change one score and predict the new total and average. Then add a sixth slot to the declaration and one more assignment.
Delete one of the five assignments and run again. The total changes but nothing crashes: an unfilled slot is simply zero, which is the kind of bug that takes a while to see.
Reverse a row in place
Run it, then change the six starting values by editing the fill line. Predict the printed row before running it again.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does high(scores) return for scores: array[1..5] of integer?
