Arrays¶
An array is one name for an ordered list of values, such as twelve monthly temperatures or an 8 by 8 game board. This page shows how to declare an array in d/BASIC, read and write its elements, ask it for its own bounds, resize it, pass it whole to a procedure, and use the three statements that act on a whole array at once: ARRAYCOPY, ARRAYFILL and ARRAYSWAP. If you have written BASIC before, subscripting works exactly the way you remember; the whole-array statements are the new part, and they exist so that a compiler and a very small runtime can do whole-array work well.
Declare an array¶
Each dimension names either an upper bound on its own or an explicit lower TO upper pair. When you give only the upper bound, OPTION BASE supplies the lower one:
An array can have up to eight dimensions. Bounds and subscripts are converted to INTEGER.
Read and write elements¶
An element behaves like a variable of the array's element type. When you assign to it, the value is converted to that type before it is stored.
Stay inside the declared bounds. d/BASIC checks every index; an out-of-range index stops the program with an error instead of quietly reading or writing unrelated memory.
Ask for the bounds¶
LBOUND and UBOUND report the array's current lower and upper bounds. Pass a second argument to ask about a particular dimension:
FOR Row% = LBOUND(Board%, 1) TO UBOUND(Board%, 1)
FOR Column% = LBOUND(Board%, 2) TO UBOUND(Board%, 2)
PRINT Board%(Row%, Column%);
NEXT Column%
PRINT
NEXT Row%
Without the dimension argument, dimension 1 is used.
Resize and erase¶
REDIM gives a dynamic array a new shape:
REDIM PRESERVE changes the bounds and keeps the values that fall inside both the old shape and the new one:
REDIM PRESERVE does not work on arrays of TYPE records (a TYPE is a record: a group of named fields that travels as one value; see Records & Objects). Use ordinary REDIM, which replaces the whole array in one step, or copy the record elements yourself when keeping them is part of your algorithm.
ERASE lets go of the array's storage and leaves the name empty:
On this page, the link between an array's name and the storage that holds its elements is called its binding. After ERASE the binding is null: the name still exists but owns no storage at all. That is a real state of its own, not an empty one-dimensional array whose upper bound happens to be -1, and it matters to the whole-array statements below.
Whole-array parameters¶
Empty parentheses after a parameter name mean "the whole array", not one element:
DECLARE FUNCTION Sum& (Values() AS INTEGER)
FUNCTION Sum& (Values() AS INTEGER)
DIM Total AS LONG
FOR I% = LBOUND(Values) TO UBOUND(Values)
Total = Total + Values(I%)
NEXT I%
Sum& = Total
END FUNCTION
The procedure sees the array's current bounds. A whole-array parameter is not an element, not a slice of the array, and not an untyped bag of values.
The three whole-array verbs¶
d/BASIC has three statements that act on a whole array at once. This page calls them verbs:
| Goal | Syntax | What moves | What gets allocated |
|---|---|---|---|
| copy | ARRAYCOPY Source() TO Destination() |
every element, into storage of its own | the destination becomes a fresh copy |
| fill | ARRAYFILL Destination(), Value |
one converted value into every element | same element type, dimensions and bounds; replaced all at once |
| swap | ARRAYSWAP A(), B() |
only the two bindings | no element is copied and no new array is allocated |
Every operand written with () must be a bare whole array: A(), never A, A(I), Object.A() or a range of elements.
The three verbs accept arrays whose elements are numbers, variable-length STRINGs, or CLASS references (a CLASS is an object with an identity of its own; see Records & Objects). They do not accept arrays of TYPE records. Copy, initialize or exchange record elements yourself, one at a time, so that your source shows plainly where record values are being copied.
ARRAYCOPY: make an independent array¶
DIM Draft%(1 TO 4)
DIM Published%()
Draft%(1) = 10
Draft%(2) = 20
ARRAYCOPY Draft%() TO Published%()
Draft%(1) = 99
PRINT Published%(1) ' 10: this is a separate binding
Use a copy when both arrays must go on changing independently of each other.
ARRAYFILL: one meaning, every element NEW¶
The value is converted exactly as one element assignment would convert it:
Fill keeps the destination's element type, number of dimensions and bounds. It is all-or-nothing: if the operation has to stop partway through its preparation, the old array stays in place unchanged rather than leaving you a half-filled one. Keeping that promise can require one temporary allocation the size of the array.
Filling an array you have ERASEd stops with an error, because there is no shape left to fill.
Why it is a language verb¶
This loop is readable:
But ARRAYFILL Plane%(), -1 says more. It promises that the order of the elements does not matter, it lets the compiler's optimizer see the operation as a whole, it compiles to a single instruction in the compiled program file, .dbc, and it lets the d/OS runtime, the program that runs your compiled code, pick the best memory route on each machine while still filling the array all at once or not at all. (d/OS is the operating system that brings modern software to 8-bit home computers such as the Atari 800, the Commodore 64 and the Apple II.)
ARRAYSWAP: change ownership, not elements NEW¶
DIM Front%(1 TO 1000)
DIM Back%(1 TO 1000)
' Draw from Front while preparing the next state in Back.
ARRAYFILL Back%(), 0
' ...modify Back()...
ARRAYSWAP Front%(), Back%()
ARRAYSWAP exchanges the two bindings. Each array's contents, lower and upper bounds and number of dimensions travel with its binding. No elements are copied, allocated, kept or released.
The two arrays must have the same element type. When both hold storage, they must have the same number of dimensions; their bounds may differ:
DIM Live%(1 TO 3)
DIM Held%(0 TO 1)
ARRAYSWAP Live%(), Held%()
PRINT LBOUND(Live%); UBOUND(Live%) ' 0 1
A null binding (one left empty by ERASE) is allowed, and swaps as null. That makes a fast "take this array and leave nothing behind" handoff possible:
DIM Ready%(1 TO 3)
DIM Taken%(1 TO 3)
ERASE Taken%
ARRAYSWAP Ready%(), Taken%()
' Taken now owns the former Ready array; Ready is null.
The memory trick to remember¶
Fill changes the values. Swap changes the name that owns them. If you find yourself copying a large back buffer just to make it the current one, you probably wanted ARRAYSWAP.
Classic scalar SWAP¶
SWAP exchanges two single values, or two records of compatible type:
It is deliberately different from ARRAYSWAP, whose operands are whole arrays.
Arrays of records and objects¶
TYPE Point
X AS INTEGER
Y AS INTEGER
END TYPE
DIM Path(0 TO 31) AS Point
DIM Buttons(0 TO 7) AS WorksButton
WorksButton is a button class from the widget library of d/Works, the office suite being written in d/BASIC (d/Write, d/Calc, d/Present). Record elements are stored inline, as values. Class elements are references to objects, and start out as NOTHING, the empty reference; assigning an object stores a counted reference to it. The difference between the two is explained in Records & Objects.
These are ordinary arrays, but the three whole-array verbs above apply only to arrays of numbers, variable-length strings and class references, not to arrays of TYPE records.
Patterns worth teaching¶
A ring without mysterious memory¶
A table driven by bounds¶
Do not repeat 1 TO 100 in every loop. Let the array report its own bounds.
Front/back state¶
Prepare the next state in Back(), then ARRAYSWAP Front(), Back(). Anyone reading Front() sees either the old complete state or the new complete state, never your half-built one.
Quick rules¶
- Bounds are inclusive.
- The default lower bound is
0unless one module-levelOPTION BASE 1says otherwise. - Use
LBOUNDandUBOUNDin procedures meant to be reused. - Use
ERASEto release an array's storage. - Use
ARRAYCOPYfor independence,ARRAYFILLfor a uniform state, andARRAYSWAPto exchange ownership. - Use those three verbs only with arrays of numbers, variable-length strings or class references.
- The parentheses on a whole array are not decoration; they tell the compiler you mean the whole array, not one element.
Next: Records & Objects →