1712 lines
71 KiB
Plaintext
1712 lines
71 KiB
Plaintext
@database beginner.guide
|
|
|
|
@Master beginner
|
|
|
|
@Width 75
|
|
|
|
|
|
This is the AmigaGuide® file beginner.guide, produced by Makeinfo-1.55 from
|
|
the input file beginner.
|
|
|
|
|
|
@NODE "main" "Types"
|
|
@Next "MoreExpressions.guide/main"
|
|
@Prev "Constants.guide/main"
|
|
@Toc "Contents.guide/main"
|
|
|
|
Types
|
|
*****
|
|
|
|
We've already met the @{b }LONG@{ub } type and found that this was the normal type
|
|
for variables (see @{"Variable types" Link "Introduction.guide/Variable types" }). The types @{b }INT@{ub } and @{b }LIST@{ub } were also
|
|
mentioned. Learning how to use types in an effective and readable way is
|
|
very important. The type of a variable (as well as its name) can give
|
|
clues to the reader about how or for what it is used. There are also more
|
|
fundamental reasons for needing types, e.g., to logically group data using
|
|
objects (see @{"OBJECT Type" Link "OBJECT Type" }).
|
|
|
|
This is a very large chapter and you might like to take it slowly. One
|
|
of the most important things to get to grips with is @{fg shine }pointers@{fg text }.
|
|
Concentrate on trying to understand these as they play a large part in any
|
|
kind of system programming.
|
|
|
|
|
|
@{" LONG Type " Link "LONG Type" }
|
|
@{" PTR Type " Link "PTR Type" }
|
|
@{" ARRAY Type " Link "ARRAY Type" }
|
|
@{" OBJECT Type " Link "OBJECT Type" }
|
|
@{" LIST and STRING Types " Link "LIST and STRING Types" }
|
|
@{" Linked Lists " Link "Linked Lists" }
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "LONG Type" "LONG Type"
|
|
@Next "PTR Type"
|
|
@Toc "main"
|
|
|
|
@{b }LONG@{ub } Type
|
|
=========
|
|
|
|
The @{b }LONG@{ub } type is the most important type because it is the default type
|
|
and by far the most common type. It can be used to store a variety of
|
|
data, including @{fg shine }memory addresses@{fg text }, as we shall see.
|
|
|
|
|
|
@{" Default type " Link "Default type" }
|
|
@{" Memory addresses " Link "Memory addresses" }
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Default type" "Default type"
|
|
@Next "Memory addresses"
|
|
@Toc "LONG Type"
|
|
|
|
Default type
|
|
------------
|
|
|
|
@{b }LONG@{ub } is the default type of variables. It is a 32-bit type, meaning
|
|
that 32-bits of memory (RAM) are used to store the data for each variable
|
|
of this type and the data can take (integer) values in the range
|
|
-2,147,483,648 to 2,147,483,647. Variables default to being @{b }LONG@{ub } typed,
|
|
but they can also be explicitly declared as @{b }LONG@{ub }:
|
|
|
|
DEF x:LONG, y
|
|
|
|
PROC fred(p:LONG, q, r:LONG)
|
|
DEF zed:LONG
|
|
@{fg shine }statements@{fg text }
|
|
ENDPROC
|
|
|
|
The global variable @{b }x@{ub }, procedure parameters @{b }p@{ub } and @{b }r@{ub }, and local variable
|
|
@{b }zed@{ub } have all been declared to be @{b }LONG@{ub } values. The declarations are
|
|
very similar to the kinds we've seen before, except that the variables
|
|
have @{b }:LONG@{ub } after their name in the declaration. This is the way the type
|
|
of a variable is given. Note that the global variable @{b }y@{ub } and the procedure
|
|
parameter @{b }q@{ub } are also @{b }LONG@{ub }, since they do not have a type specified and
|
|
@{b }LONG@{ub } is the default type for variables.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Memory addresses" "Memory addresses"
|
|
@Prev "Default type"
|
|
@Toc "LONG Type"
|
|
|
|
Memory addresses
|
|
----------------
|
|
|
|
There's a very good reason why @{b }LONG@{ub } is the normal type. A 32-bit
|
|
(integer) value can be used as a @{fg shine }memory address@{fg text }. Therefore we can store
|
|
the address (or location) of data in a variable (the variable is then
|
|
called a @{fg shine }pointer@{fg text }). The variable would then not contain the value of the
|
|
data but a way of finding the data. Once the data location is known the
|
|
data can be read or even altered! The next section covers pointers and
|
|
addresses in more detail. See @{"PTR Type" Link "PTR Type" }.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "PTR Type" "PTR Type"
|
|
@Next "ARRAY Type"
|
|
@Prev "LONG Type"
|
|
@Toc "main"
|
|
|
|
@{b }PTR@{ub } Type
|
|
========
|
|
|
|
The @{b }PTR@{ub } type is used to hold memory addresses. Variables which have a
|
|
@{b }PTR@{ub } type are called @{fg shine }pointers@{fg text } (since they store memory addresses, as
|
|
mentioned in the previous section). This section describes, in detail,
|
|
addresses, pointers and the @{b }PTR@{ub } type.
|
|
|
|
|
|
@{" Addresses " Link "Addresses" }
|
|
@{" Pointers " Link "Pointers" }
|
|
@{" Indirect types " Link "Indirect types" }
|
|
@{" Finding addresses (making pointers) " Link "Finding addresses (making pointers)" }
|
|
@{" Extracting data (dereferencing pointers) " Link "Extracting data (dereferencing pointers)" }
|
|
@{" Procedure parameters " Link "Procedure parameters" }
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Addresses" "Addresses"
|
|
@Next "Pointers"
|
|
@Toc "PTR Type"
|
|
|
|
Addresses
|
|
---------
|
|
|
|
Every piece of data a program uses is stored somewhere in the
|
|
computer's memory,and this includes the data contained in variables. So,
|
|
when you assign one to the variable @{b }x@{ub } you are actually storing one in the
|
|
location reserved for @{b }x@{ub } in the computer's memory. A location in memory is
|
|
known as a @{fg shine }memory address@{fg text }, and this is just a 32-bit number (so can be
|
|
stored in a @{b }LONG@{ub } variable). If you know the location of a variable's data
|
|
then you can read the value and you can also change it.
|
|
|
|
To understand memory addresses, a good analogy is to think of memory as
|
|
a road or street, each memory location as a post-box on a house, and each
|
|
piece of data as a letter. If you were a postman you would need to know
|
|
where to put your letters, and this information is given by the address of
|
|
the post-box. As time goes by, each post-box is filled with different
|
|
letters. This is like the value in a memory location (or variable)
|
|
changing. To change the letters stored in your post-box, you tell your
|
|
friends your address and they can send letters in and fill it. This is
|
|
like letting some part of a program change your data by giving it the
|
|
address of the data.
|
|
|
|
The next two diagrams illustrate this analogy. A letter contains an
|
|
address which points to a particular house (or lot of mail) on a street.
|
|
|
|
+-------+
|
|
| Letter|
|
|
|-------|
|
|
|Address+----*
|
|
+-------+ \\
|
|
\\
|
|
\\
|
|
+--------+ +---\\----+ +--------+ +--------+
|
|
| House | | House | | House | | House |
|
|
Street: |+------+| |+------+| |+------+| ... |+------+|
|
|
|| Mail || || Mail || || Mail || || Mail ||
|
|
+========+ +========+ +========+ +========+
|
|
|
|
A pointer contains an address which points to a variable (or data) in
|
|
memory.
|
|
|
|
+-------+
|
|
|Pointer|
|
|
|-------|
|
|
|Address+----*
|
|
+-------+ \\
|
|
\\
|
|
\\
|
|
+--------+ +---\\----+ +--------+ +--------+
|
|
|Variable| |Variable| |Variable| |Variable|
|
|
Memory: |+------+| |+------+| |+------+| ... |+------+|
|
|
|| Data || || Data || || Data || || Data ||
|
|
+========+ +========+ +========+ +========+
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Pointers" "Pointers"
|
|
@Next "Indirect types"
|
|
@Prev "Addresses"
|
|
@Toc "PTR Type"
|
|
|
|
Pointers
|
|
--------
|
|
|
|
Variables which contain memory addresses are called @{fg shine }pointers@{fg text }. As we
|
|
saw in the previous section, we can store memory addresses in @{b }LONG@{ub }
|
|
variables. However, we then don't know the type of the data stored at
|
|
those addresses. If it is important (or useful) to know this then the @{b }PTR@{ub }
|
|
type (or, more accurately, one of the many @{b }PTR@{ub } types) should be used.
|
|
|
|
DEF p:PTR TO LONG, i:PTR TO INT,
|
|
cptr:PTR TO CHAR, gptr:PTR TO gadget
|
|
|
|
The values stored in each of @{b }p@{ub }, @{b }cptr@{ub }, @{b }i@{ub } and @{b }gptr@{ub } are @{b }LONG@{ub } since they are
|
|
memory addresses. However, the data at the address stored in @{b }p@{ub } is taken
|
|
to be @{b }LONG@{ub } (a 32-bit value), that at @{b }cptr@{ub } is @{b }CHAR@{ub } (an 8-bit value), that
|
|
at @{b }i@{ub } is @{b }INT@{ub } (a 16-bit value), and that at @{b }gptr@{ub } is @{b }gadget@{ub }, which is an
|
|
@{fg shine }object@{fg text } (see @{"OBJECT Type" Link "OBJECT Type" }).
|
|
|
|
Since pointers are just data like any other @{b }LONG@{ub } variable, the value of
|
|
the pointer is somewhere in memory. This means it has an address, so you
|
|
can have a pointer which is actually pointing to another pointer! This is
|
|
one of the reasons pointers can be quite difficult to think about, and
|
|
misunderstanding them is often the cause of big problems with programs.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Indirect types" "Indirect types"
|
|
@Next "Finding addresses (making pointers)"
|
|
@Prev "Pointers"
|
|
@Toc "PTR Type"
|
|
|
|
Indirect types
|
|
--------------
|
|
|
|
In the previous example we saw @{b }INT@{ub } and @{b }CHAR@{ub } used as the destination
|
|
types of pointers, and these are the 16- and 8-bit equivalents
|
|
(respectively) of the @{b }LONG@{ub } type. However, unlike @{b }LONG@{ub } these types cannot
|
|
be used directly to declare global or local variables, or procedure
|
|
parameters. They can only be used in constructing types (for instance
|
|
with @{b }PTR TO@{ub }). The following declarations are therefore @{i }illegal@{ui }, and it
|
|
might be nice to try compiling a little program with such a declaration,
|
|
just to see the error message the E compiler gives.
|
|
|
|
/* This program fragment contains illegal declarations */
|
|
DEF c:CHAR, i:INT
|
|
|
|
/* This program fragment contains illegal declarations */
|
|
PROC fred(a:INT, b:CHAR)
|
|
DEF x:INT
|
|
@{fg shine }statements@{fg text }
|
|
ENDPROC
|
|
|
|
This is not much of a limitation because you can store @{b }INT@{ub } or @{b }CHAR@{ub }
|
|
values in @{b }LONG@{ub } variables if you really need to. However, it does mean
|
|
there's a nice, simple rule: every direct value in E is a 32-bit quantity,
|
|
either a @{b }LONG@{ub } or a pointer. In fact, @{b }LONG@{ub } is actually short-hand for @{b }PTR
|
|
TO CHAR@{ub }, so you can use @{b }LONG@{ub } values like they were actually @{b }PTR TO CHAR@{ub }
|
|
values.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Finding addresses (making pointers)" "Finding addresses (making pointers)"
|
|
@Next "Extracting data (dereferencing pointers)"
|
|
@Prev "Indirect types"
|
|
@Toc "PTR Type"
|
|
|
|
Finding addresses (making pointers)
|
|
-----------------------------------
|
|
|
|
If a program knows the address of a variable it can directly read or
|
|
alter the value stored in the variable. To obtain the address of a simple
|
|
variable you use @{b }{@{ub } and @{b }}@{ub } around the variable name. The address of
|
|
non-simple variables (e.g., objects and arrays) can be found much more
|
|
easily (see the appropriate section), and in fact you will very rarely
|
|
need to use @{b }{@{ub }@{fg shine }var@{fg text } @{b } }@{ub }. However, if you understand how to explicitly
|
|
make pointers with @{b }{@{ub }@{fg shine }var@{fg text } @{b } }@{ub } and use the pointers to get to data, then
|
|
you'll understand the way pointers are used for the non-simple types much
|
|
more quickly.
|
|
|
|
Addresses can be stored in a variable, passed to a procedure or
|
|
whatever (they're just 32-bit values). Try out the following program:
|
|
|
|
DEF x
|
|
|
|
PROC main()
|
|
fred(2)
|
|
ENDPROC
|
|
|
|
PROC fred(y)
|
|
DEF z
|
|
WriteF('x is at address \\d\\n', {x})
|
|
WriteF('y is at address \\d\\n', {y})
|
|
WriteF('z is at address \\d\\n', {z})
|
|
WriteF('fred is at address \\d\\n', {fred})
|
|
ENDPROC
|
|
|
|
Notice that you can also find the address of a procedure using @{b }{@{ub } and @{b }}@{ub }.
|
|
This is the memory location of the code the procedure represents, although
|
|
it is not something we need concern ourselves with any further in this
|
|
Guide. Here's the output from one execution of this program (don't expect
|
|
your output to be exactly the same, though):
|
|
|
|
x is at address 3758280
|
|
y is at address 3758264
|
|
z is at address 3758252
|
|
fred is at address 3732878
|
|
|
|
This is an interesting program to run several times under different
|
|
circumstances. You should see that sometimes the numbers for the
|
|
addresses change. Running the program when another is multi-tasking (and
|
|
eating memory) should produce the best changes, whereas running it
|
|
consecutively (in one CLI) should produce the smallest (if any) changes.
|
|
This gives you a glimpse at the complex memory handling of the Amiga and
|
|
the E compiler.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Extracting data (dereferencing pointers)" "Extracting data (dereferencing pointers)"
|
|
@Next "Procedure parameters"
|
|
@Prev "Finding addresses (making pointers)"
|
|
@Toc "PTR Type"
|
|
|
|
Extracting data (dereferencing pointers)
|
|
----------------------------------------
|
|
|
|
If you have an address stored in a variable (i.e., a pointer) you can
|
|
extract the data using the @{b }^@{ub } operator. This is called @{fg shine }dereferencing@{fg text } the
|
|
pointer. The @{b }^@{ub } operator should only really be used when @{b }{@{ub }@{fg shine }var@{fg text } @{b } }@{ub } has
|
|
been used to obtain an address. To this end, @{b }LONG@{ub } values are read and
|
|
written when dereferencing pointers in this way. For pointers to
|
|
non-simple types (e.g., objects and arrays), dereferencing is achieved in
|
|
much more readable ways (see the appropriate section for details), and
|
|
this operator is not used. In fact, @{b } ^@{ub }@{fg shine }var@{fg text }@{b }@{ub } is seldom used in programs,
|
|
but is useful for explaining how pointers work, especially in conjunction
|
|
with @{b }{@{ub }@{fg shine }var@{fg text } @{b } }@{ub }.
|
|
|
|
Using pointers can remove the scope restriction on local variables,
|
|
i.e., they can be altered from outside the procedure for which they are
|
|
local. Whilst this kind of use is not generally advised, it makes for a
|
|
good example which shows the power of pointers. For example, the
|
|
following program changes the value of the local variable @{b }x@{ub } for the
|
|
procedure @{b }fred@{ub } from within the procedure @{b }barney@{ub }. It can do this only
|
|
because @{b }fred@{ub } passes a pointer to @{b }x@{ub } as a parameter to @{b }barney@{ub }.
|
|
|
|
PROC main()
|
|
fred()
|
|
ENDPROC
|
|
|
|
PROC fred()
|
|
DEF x, p:PTR TO LONG
|
|
x:=33
|
|
p:={x}
|
|
barney(p)
|
|
WriteF('x is now \\d\\n', x)
|
|
ENDPROC
|
|
|
|
PROC barney(ptr:PTR TO LONG)
|
|
DEF val
|
|
val:=^ptr
|
|
^ptr:=val-6
|
|
ENDPROC
|
|
|
|
Here's what you can expect it to generate as output:
|
|
|
|
x is now 27
|
|
|
|
Notice that the @{b }^@{ub } operator (i.e., dereferencing) is quite versatile. In
|
|
the first assignment of the procedure @{b }barney@{ub } it is used (with the pointer
|
|
@{b }ptr@{ub }) to get the value stored in the local variable @{b }x@{ub }, and in the second it
|
|
is used to change this variable's value. In either case, dereferencing
|
|
makes the pointer behave exactly as if you'd written the variable to which
|
|
it points. To emphasise this, we can remove the @{b }barney@{ub } procedure, like we
|
|
did above (see @{"Style Reuse and Readability" Link "Introduction.guide/Style Reuse and Readability" }):
|
|
|
|
PROC main()
|
|
fred()
|
|
ENDPROC
|
|
|
|
PROC fred()
|
|
DEF x, p:PTR TO LONG, val
|
|
x:=33
|
|
p:={x}
|
|
val:=x
|
|
x:=val-6
|
|
WriteF('x is now \\d\\n', x)
|
|
ENDPROC
|
|
|
|
Everywhere the @{b }barney@{ub } procedure used @{b }^ptr@{ub } we've written @{b }x@{ub } (because we are
|
|
now in the procedure for which @{b }x@{ub } is local). We've also eliminated the @{b }ptr@{ub }
|
|
variable (the parameter to the @{b }barney@{ub } procedure), since it was only used
|
|
with the @{b }^@{ub } operator.
|
|
|
|
To make things clear the @{b }fred@{ub } and @{b }barney@{ub } example is deliberately
|
|
`wordy'. The @{b }val@{ub } and @{b }p@{ub } variables are unnecessary, and the pointer types
|
|
could be abbreviated to @{b }LONG@{ub } or even omitted, for the reasons outlined
|
|
above (see @{"LONG Type" Link "LONG Type" }). This is the compact form of the example:
|
|
|
|
PROC main()
|
|
fred()
|
|
ENDPROC
|
|
|
|
PROC fred()
|
|
DEF x
|
|
x:=33
|
|
barney({x})
|
|
WriteF('x is now \\d\\n', x)
|
|
ENDPROC
|
|
|
|
PROC barney(ptr)
|
|
^ptr:=^ptr-6
|
|
ENDPROC
|
|
|
|
By far the most common use of pointers is to address (or reference)
|
|
large structures of data. It would be extremely expensive (in terms of
|
|
CPU time) to pass large amounts of data from procedure to procedure, so
|
|
addresses to such data are passed instead (and, as we know, these are just
|
|
32-bit values). The Amiga system functions (such as ones for creating
|
|
windows) require a lot of structured data, so if you plan to do any real
|
|
programming you are going to have to understand and use pointers.
|
|
|
|
As we have seen, if you have a pointer to some data you can easily read
|
|
the data, but you can just as easily alter it. If you want to write code
|
|
that is clear and understandable, you have an implicit responsibility to
|
|
not use pointers to alter data that you didn't ought to. For instance, if
|
|
a procedure is passed a pointer which it then uses to change the data
|
|
being pointed to, then it ought to be well documented (using comments)
|
|
exactly what changes it makes.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Procedure parameters" "Procedure parameters"
|
|
@Prev "Extracting data (dereferencing pointers)"
|
|
@Toc "PTR Type"
|
|
|
|
Procedure parameters
|
|
--------------------
|
|
|
|
Only local and global variables have the luxury of a large choice of
|
|
types. Procedure parameters can only be @{b }LONG@{ub } or @{b }PTR TO @{ub }@{fg shine }type@{fg text }@{b }@{ub }. This is
|
|
not really a big limitation as we shall see in the later sections.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "ARRAY Type" "ARRAY Type"
|
|
@Next "OBJECT Type"
|
|
@Prev "PTR Type"
|
|
@Toc "main"
|
|
|
|
@{b }ARRAY@{ub } Type
|
|
==========
|
|
|
|
Quite often, the data used by a program needs to be ordered in some
|
|
way, primarily so that it can be accessed easily. E provides a way to
|
|
achieve such simple ordering: the @{b }ARRAY@{ub } type. This type (in its various
|
|
forms) is common to most computer languages.
|
|
|
|
|
|
@{" Tables of data " Link "Tables of data" }
|
|
@{" Accessing array data " Link "Accessing array data" }
|
|
@{" Array pointers " Link "Array pointers" }
|
|
@{" Point to other elements " Link "Point to other elements" }
|
|
@{" Array procedure parameters " Link "Array procedure parameters" }
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Tables of data" "Tables of data"
|
|
@Next "Accessing array data"
|
|
@Toc "ARRAY Type"
|
|
|
|
Tables of data
|
|
--------------
|
|
|
|
Data can be grouped together in many different ways, but probably the
|
|
most common and straight-forward way is to make a table. In a table the
|
|
data is ordered either vertically or horizontally, but the important thing
|
|
is the relative positioning of the elements. The E view of this kind of
|
|
ordered data is the @{b }ARRAY@{ub } type. An @{fg shine }array@{fg text } is just a fixed sized
|
|
collection of data in order. The size of an array is important and this
|
|
is fixed when it is declared. The following illustrates array
|
|
declarations:
|
|
|
|
DEF a[132]:ARRAY,
|
|
table[21]:ARRAY OF LONG,
|
|
ints[3]:ARRAY OF INT,
|
|
objs[54]:ARRAY OF myobject
|
|
|
|
The size of the array is given in the square brackets (@{b }[@{ub } and @{b }]@{ub }). The type
|
|
of the elements in the array defaults to @{b }CHAR@{ub }, but this can be given
|
|
explicitly using the @{b }OF@{ub } keyword and the type name. However, only @{b }LONG@{ub },
|
|
@{b }INT@{ub }, @{b }CHAR@{ub } and object types are allowed (@{b }LONG@{ub } can hold pointer values
|
|
so this isn't much of a limitation). Object types are described below
|
|
(see @{"OBJECT Type" Link "OBJECT Type" }).
|
|
|
|
As mentioned above, procedure parameters cannot be arrays (see
|
|
@{"Procedure parameters" Link "Procedure parameters" }). We will overcome this apparent limitation soon
|
|
(see @{"Array procedure parameters" Link "Array procedure parameters" }).
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Accessing array data" "Accessing array data"
|
|
@Next "Array pointers"
|
|
@Prev "Tables of data"
|
|
@Toc "ARRAY Type"
|
|
|
|
Accessing array data
|
|
--------------------
|
|
|
|
To access a particular element in an array you use square brackets
|
|
again, this time specifying the @{fg shine }index@{fg text } (or position) of the element you
|
|
want. Indices start at zero for the first element of the array, one for
|
|
the second element and, in general, (n-1) for the n-th element. This may
|
|
seem strange at first, but it's the way most computer languages do it! We
|
|
will see a reason why this makes sense soon (see @{"Array pointers" Link "Array pointers" }).
|
|
|
|
DEF a[10]:ARRAY
|
|
|
|
PROC main()
|
|
DEF i
|
|
FOR i:=0 TO 9
|
|
a[i]:=i*i
|
|
ENDFOR
|
|
WriteF('The 7th element of the array a is \\d\\n', a[6])
|
|
a[a[2]]:=10
|
|
WriteF('The array is now:\\n')
|
|
FOR i:=0 TO 9
|
|
WriteF(' a[\\d] = \\d\\n', i, a[i])
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
This should all seem very straight-forward although one of the lines looks
|
|
a bit complicated. Try to work out what happens to the array after the
|
|
assignment immediately following the first @{b }WriteF@{ub }. In this assignment the
|
|
index comes from a value stored in the array itself! Be careful when
|
|
doing complicated things like this, though: make sure you don't try to
|
|
read data from or write data to elements beyond the end of the array. In
|
|
our example there are only ten elements in the array @{b }a@{ub }, so it wouldn't be
|
|
sensible to talk about the eleventh element. The program could have
|
|
checked that the value stored at @{b }a[2]@{ub } was a number between zero and nine
|
|
before trying to access that array element, but it wasn't necessary in
|
|
this case. Here's the output this example should generate:
|
|
|
|
The 7th element of the array a is 36
|
|
The array is now:
|
|
a[0] = 0
|
|
a[1] = 1
|
|
a[2] = 4
|
|
a[3] = 9
|
|
a[4] = 10
|
|
a[5] = 25
|
|
a[6] = 36
|
|
a[7] = 49
|
|
a[8] = 64
|
|
a[9] = 81
|
|
|
|
If you do try to write to a non-existent array element strange things
|
|
can happen. This may be practically unnoticeable (like corrupting some
|
|
other data), but if you're really unlucky you might crash your computer.
|
|
The moral is: stay within the bounds of the array.
|
|
|
|
A short-hand for the first element of an array (i.e., the one with an
|
|
index of zero) is to omit the index and write only the square brackets.
|
|
Therefore, @{b }a[]@{ub } is the same as @{b }a[0]@{ub }.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Array pointers" "Array pointers"
|
|
@Next "Point to other elements"
|
|
@Prev "Accessing array data"
|
|
@Toc "ARRAY Type"
|
|
|
|
Array pointers
|
|
--------------
|
|
|
|
When you declare an array, the address of the (beginning of the) array
|
|
is given by the variable name without square brackets. Consider the
|
|
following program:
|
|
|
|
DEF a[10]:ARRAY OF INT
|
|
|
|
PROC main()
|
|
DEF ptr:PTR TO INT, i
|
|
FOR i:=0 TO 9
|
|
a[i]:=i
|
|
ENDFOR
|
|
ptr:=a
|
|
ptr++
|
|
ptr[]:=22
|
|
FOR i:=0 TO 9
|
|
WriteF('a[\\d] is \\d\\n', i, a[i])
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
Here's the output from it:
|
|
|
|
a[0] is 0
|
|
a[1] is 22
|
|
a[2] is 2
|
|
a[3] is 3
|
|
a[4] is 4
|
|
a[5] is 5
|
|
a[6] is 6
|
|
a[7] is 7
|
|
a[8] is 8
|
|
a[9] is 9
|
|
|
|
You should notice that the second element of the array has been changed
|
|
using the pointer. The assignment to @{b }ptr@{ub } initialises it to point to the
|
|
start of the array @{b }a@{ub }, and then the @{b }ptr++@{ub } statement increments @{b }ptr@{ub } to point
|
|
to the next element of the array. It is vital that @{b }ptr@{ub } is declared as @{b }PTR
|
|
TO INT@{ub } since the array is an @{b }ARRAY OF INT@{ub }. The @{b }[]@{ub } is used to dereference
|
|
@{b }ptr@{ub } and therefore 22 is stored in the second element of the array. In
|
|
fact, the @{b }ptr@{ub } can be used in exactly the same way as an array, so @{b }ptr[1]@{ub }
|
|
would be the next (or third element) of the array @{b }a@{ub }. Also, since @{b }ptr@{ub }
|
|
points to the second element of @{b }a@{ub }, negative values may legitimately be
|
|
used as the index, and @{b }ptr[-1]@{ub } is the first element of @{b }a@{ub }.
|
|
|
|
In fact, the following declarations are identical except the first
|
|
reserves an appropriate amount of memory for the array whereas the second
|
|
relies on you having done this somewhere else in the program.
|
|
|
|
DEF a[20]:ARRAY OF INT
|
|
|
|
DEF a:PTR TO INT
|
|
|
|
The following diagram is similar to the diagrams given earlier (see
|
|
@{"Addresses" Link "Addresses" }). It is an illustration of an array, @{b }a@{ub }, which was declared to
|
|
be an array of twenty @{b }INT@{ub }s.
|
|
|
|
+--------+
|
|
|Variable|
|
|
| 'a' |
|
|
|--------|
|
|
| Address+----*
|
|
+--------+ \\
|
|
\\
|
|
\\
|
|
+-------+ +--\\----+ +-------+ +-------+ +-------+
|
|
|Unknown| | a[0] | | a[1] | | a[19] | |Unknown|
|
|
Memory: |+-----+| |+-----+| |+-----+| ... |+-----+| |+-----+|
|
|
|| XXX || || INT || || INT || || INT || || XXX ||
|
|
+=======+ +=======+ +=======+ +=======+ +=======+
|
|
|
|
As you can see, the variable @{b }a@{ub } is a pointer to the reserved chunk of
|
|
memory which contains the array elements. Parts of memory that aren't
|
|
between @{b }a[0]@{ub } and @{b }a[19]@{ub } are marked as `Unknown' because they are not part
|
|
of the array. This memory should therefore not be accessed using the
|
|
array @{b }a@{ub }.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Point to other elements" "Point to other elements"
|
|
@Next "Array procedure parameters"
|
|
@Prev "Array pointers"
|
|
@Toc "ARRAY Type"
|
|
|
|
Point to other elements
|
|
-----------------------
|
|
|
|
We saw in the previous section how to increment a pointer so that it
|
|
points to the next element in the array. Decrementing a pointer @{b }p@{ub } (i.e.,
|
|
making it point to the previous element) is done in a similar way, using
|
|
the @{b }p--@{ub } statement. Actually, @{b }p++@{ub } and @{b }p--@{ub } are really expressions which
|
|
denote pointer values. @{b }p++@{ub } denotes the address stored in @{b }p@{ub } @{i }before@{ui } it is
|
|
incremented, and @{b }p--@{ub } denotes the address @{i }after@{ui } @{b }p@{ub } is decremented.
|
|
Therefore,
|
|
|
|
addr:=p
|
|
p++
|
|
|
|
does the same as
|
|
|
|
addr:=p++
|
|
|
|
And
|
|
|
|
p--
|
|
addr:=p
|
|
|
|
does the same as
|
|
|
|
addr:=p--
|
|
|
|
The reason why @{b }++@{ub } and @{b }--@{ub } should be used to increment and decrement a
|
|
pointer is that values from different types occupy different numbers of
|
|
memory locations. In fact, a single memory location is a @{fg shine }byte@{fg text }, and this
|
|
is eight bits. Therefore, @{b }CHAR@{ub } values occupy a single byte, whereas @{b }LONG@{ub }
|
|
values take up four bytes (32 bits). If @{b }p@{ub } were a pointer to @{b }CHAR@{ub } and it
|
|
was pointing to an array (of @{b }CHAR@{ub }) the @{b }p+1@{ub } memory location would contain
|
|
the second element of the array (and @{b }p+2@{ub } the third, etc.). But if @{b }p@{ub } were
|
|
a pointer to an array of @{b }LONG@{ub } the second element in the array would be at
|
|
@{b }p+4@{ub } (and the third at @{b }p+8@{ub }). The locations @{b }p@{ub }, @{b }p+1@{ub }, @{b }p+2@{ub } and @{b }p+3@{ub } all make up
|
|
the @{b }LONG@{ub } value at address @{b }p@{ub }. Having to remember things like this is a
|
|
pain, and it's a lot less readable than using @{b }++@{ub } or @{b }--@{ub }. However, you must
|
|
remember to declare your pointer with the correct type in order for @{b }++@{ub } and
|
|
@{b }--@{ub } to work correctly.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Array procedure parameters" "Array procedure parameters"
|
|
@Prev "Point to other elements"
|
|
@Toc "ARRAY Type"
|
|
|
|
Array procedure parameters
|
|
--------------------------
|
|
|
|
Since we now know how to get the address of an array we can simulate
|
|
passing an array as a procedure parameter by passing the address of the
|
|
array. For example, the following program uses a procedure to fill in the
|
|
first @{b }x@{ub } elements of an array with their index numbers.
|
|
|
|
DEF a[10]:ARRAY OF INT
|
|
|
|
PROC main()
|
|
DEF i
|
|
fillin(a, 10)
|
|
FOR i:=0 TO 9
|
|
WriteF('a[\\d] is \\d\\n', i, a[i])
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
PROC fillin(ptr:PTR TO INT, x)
|
|
DEF i
|
|
FOR i:=0 TO x-1
|
|
ptr[]:=i
|
|
ptr++
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
Here's the output it should generate:
|
|
|
|
a[0] is 0
|
|
a[1] is 1
|
|
a[2] is 2
|
|
a[3] is 3
|
|
a[4] is 4
|
|
a[5] is 5
|
|
a[6] is 6
|
|
a[7] is 7
|
|
a[8] is 8
|
|
a[9] is 9
|
|
|
|
The array @{b }a@{ub } only has ten elements so we shouldn't fill in any more than
|
|
the first ten elements. Therefore, in the example, the call to the
|
|
procedure @{b }fillin@{ub } should not have a bigger number than ten as the second
|
|
parameter. Also, we could treat @{b }ptr@{ub } more like an array (and not use @{b }++@{ub }),
|
|
but in this case using @{b }++@{ub } is slightly better since we are assigning to
|
|
each element in turn. The alternative definition of @{b }fillin@{ub } (without using
|
|
@{b }++@{ub }) is:
|
|
|
|
PROC fillin2(ptr:PTR TO INT, x)
|
|
DEF i
|
|
FOR i:=0 TO x-1
|
|
ptr[i]:=i
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
Also, yet another version of @{b }fillin@{ub } uses the expression form of @{b }++@{ub } in the
|
|
assignment (see @{"Assignments" Link "MoreExpressions.guide/Assignments" }) and the horizontal form of the @{b }FOR@{ub } loop to
|
|
give a really compact definition.
|
|
|
|
PROC fillin3(ptr:PTR TO INT, x)
|
|
DEF i
|
|
FOR i:=0 TO x-1 DO ptr[]++:=i
|
|
ENDPROC
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "OBJECT Type" "OBJECT Type"
|
|
@Next "LIST and STRING Types"
|
|
@Prev "ARRAY Type"
|
|
@Toc "main"
|
|
|
|
@{b }OBJECT@{ub } Type
|
|
===========
|
|
|
|
Objects are the E equivalent of C and Assembly structures, or Pascal
|
|
records. They are like arrays except the elements are named not numbered,
|
|
and they can be of different types. So, to find a particular element in
|
|
an object you use a name instead of an index (number). Objects are also
|
|
the basis of the OOP features of E (see @{"Object Oriented E" Link "OOE.guide/main" }).
|
|
|
|
|
|
@{" Example object " Link "Example object" }
|
|
@{" Element selection and element types " Link "Element selection and element types" }
|
|
@{" Amiga system objects " Link "Amiga system objects" }
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Example object" "Example object"
|
|
@Next "Element selection and element types"
|
|
@Toc "OBJECT Type"
|
|
|
|
Example object
|
|
--------------
|
|
|
|
We'll dive straight in with this first example, and define an object
|
|
and use it. Object definitions are global and must be made before any
|
|
procedure definitions.
|
|
|
|
OBJECT rec
|
|
tag, check
|
|
table[8]:ARRAY
|
|
data:LONG
|
|
ENDOBJECT
|
|
|
|
PROC main()
|
|
DEF a:rec
|
|
a.tag:=1
|
|
a.check:=a
|
|
a.data:=a.tag+(10000*a.tag)
|
|
ENDPROC
|
|
|
|
This program doesn't visibly do anything so there isn't much point in
|
|
compiling it. What it does do, however, is show how a typical object is
|
|
defined and how elements of an object are selected.
|
|
|
|
The object being defined in the example is @{b }rec@{ub }, and its elements are
|
|
defined just like variable declarations (but without a @{b }DEF@{ub }). There can be
|
|
as many lines of element definitions as you like between the @{b }OBJECT@{ub } and
|
|
@{b }ENDOBJECT@{ub } lines, and each line can contain any number of elements
|
|
separated by commas. The elements of the @{b }rec@{ub } object are @{b }tag@{ub } and @{b }check@{ub }
|
|
(which are @{b }LONG@{ub }), @{b }table@{ub } (which is an array of @{b }CHAR@{ub } with eight elements)
|
|
and @{b }data@{ub } (which is also @{b }LONG@{ub }). Every variable of @{b }rec@{ub } object type will
|
|
have space reserved for each of these elements. The declaration of the
|
|
(local) variable @{b }a@{ub } therefore reserves enough memory for one @{b }rec@{ub } object.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Element selection and element types" "Element selection and element types"
|
|
@Next "Amiga system objects"
|
|
@Prev "Example object"
|
|
@Toc "OBJECT Type"
|
|
|
|
Element selection and element types
|
|
-----------------------------------
|
|
|
|
To select elements in an object @{b }obj@{ub } you use @{b }obj.name@{ub }, where @{b }name@{ub } is one
|
|
of the element names. In the example, the @{b }tag@{ub } element of the @{b }rec@{ub } object @{b }a@{ub }
|
|
is selected by writing @{b }a.tag@{ub }. The other elements are selected in a
|
|
similar way.
|
|
|
|
Just like an array declaration the address of an object @{b }obj@{ub } is stored
|
|
in the variable @{b }obj@{ub }, and any pointer of type @{b }PTR TO @{ub }@{fg shine }objectname@{fg text }@{b }@{ub } can be
|
|
used just like an object of type @{fg shine }objectname@{fg text }. Therefore, in the previous
|
|
example @{b }a@{ub } is a @{b }PTR TO rec@{ub }.
|
|
|
|
As the example object shows, the elements of an object can have several
|
|
different types. In fact, the elements can have any type, including
|
|
object, pointer to object and array of object. The following example
|
|
shows how to access some different typed elements.
|
|
|
|
OBJECT rec
|
|
tag, check
|
|
table[8]:ARRAY
|
|
data:LONG
|
|
ENDOBJECT
|
|
|
|
OBJECT bigrec
|
|
data:PTR TO LONG
|
|
subrec:PTR TO rec
|
|
rectable[22]:ARRAY OF rec
|
|
ENDOBJECT
|
|
|
|
PROC main()
|
|
DEF r:rec, b:bigrec, rt:PTR TO rec
|
|
r.table[]:="H"
|
|
b.subrec:=r
|
|
b.subrec.tag:=1
|
|
b.subrec.data:=r.tag+(10000*b.subrec.tag)
|
|
b.subrec.table[1]:="i"
|
|
b.rectable[0].data:=r.tag
|
|
b.rectable[0].table[0]:="A"
|
|
rt:=b.rectable
|
|
rt[].data++:=0
|
|
rt[].table[]--:="B"
|
|
ENDPROC
|
|
|
|
The @{b }++@{ub } and @{b }--@{ub } operators apply to first thing in the selection (i.e., @{b }rt@{ub } in
|
|
@{i }both@{ui } the last two assignments in the example above), and may occur only
|
|
after all the selections. Notice that object selection and array indexing
|
|
can be repeated as much as necessary (but only as the types of the
|
|
elements allow). As a simple example, consider the third assignment:
|
|
|
|
b.subrec.tag:=1
|
|
|
|
This selects the @{b }subrec@{ub } element from the @{b }bigrec@{ub } object @{b }b@{ub }, and then sets
|
|
the @{b }tag@{ub } element of this @{b }rec@{ub } object to 1. Now, consider one of the later
|
|
assignments:
|
|
|
|
b.rectable[0].table[0]:="A"
|
|
|
|
This selects the @{b }rectable@{ub } element from @{b }b@{ub }, which is an array of @{b }rec@{ub } objects.
|
|
The first element of this array is selected, and then the @{b }table@{ub } element of
|
|
the @{b }rec@{ub } object is selected. Finally, the first character of the @{b }table@{ub } is
|
|
set to the character @{b }A@{ub }.
|
|
|
|
As you can probably tell, it is important to give the elements of
|
|
objects appropriate types if you want to do multiple selection in this way.
|
|
However, this is not always possible or the best way of doing some things,
|
|
so there is a way of giving a different type to pointers (this is called
|
|
@{fg shine }explicit pointer typing@{fg text }--see the `Reference Manual' for more details).
|
|
|
|
Here's a quite simple example which uses an array of objects:
|
|
|
|
OBJECT rec
|
|
tag, check
|
|
table[8]:ARRAY
|
|
data:LONG
|
|
ENDOBJECT
|
|
|
|
PROC main()
|
|
DEF a[10]:ARRAY OF rec, p:PTR TO rec, i
|
|
p:=a
|
|
FOR i:=0 TO 9
|
|
a[i].tag:=i
|
|
p.check++:=i
|
|
ENDFOR
|
|
FOR i:=0 TO 9
|
|
IF a[i].tag<>a[i].check
|
|
WriteF('Whoops, a[\\d] went wrong...\\n', i)
|
|
ENDIF
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
If you think about it for long enough you'll see that @{b }a[0].tag@{ub } is the same
|
|
as @{b }a.tag@{ub }. That's because @{b }a@{ub } is a pointer to the first element of the
|
|
array, and the elements of the array are objects. Therefore, @{b }a@{ub } is a
|
|
pointer to an object (the first object in the array).
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Amiga system objects" "Amiga system objects"
|
|
@Prev "Element selection and element types"
|
|
@Toc "OBJECT Type"
|
|
|
|
Amiga system objects
|
|
--------------------
|
|
|
|
There are many different Amiga system objects. For instance, there's
|
|
one which contains the information needed to make a gadget (like the
|
|
`close' gadget on most windows), and one which contains all the
|
|
information about a process or task. These objects are vitally important
|
|
and so are supplied with E in the form of `modules'. Each module is
|
|
specific to a certain area of the Amiga system and contains object and
|
|
other definitions. Modules are discussed in more detail later (see
|
|
@{"Modules" Link "Modules.guide/main" }).
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "LIST and STRING Types" "LIST and STRING Types"
|
|
@Next "Linked Lists"
|
|
@Prev "OBJECT Type"
|
|
@Toc "main"
|
|
|
|
@{b }LIST@{ub } and @{b }STRING@{ub } Types
|
|
=====================
|
|
|
|
Arrays are common to many computer languages. However, they can be a
|
|
bit of a pain because you always need to make sure you haven't run off the
|
|
end of the array when you're writing to it. This is where the @{b }STRING@{ub } and
|
|
@{b }LIST@{ub } types come in. @{b }STRING@{ub } is very much like @{b }ARRAY OF CHAR@{ub } and @{b }LIST@{ub } is
|
|
like @{b }ARRAY OF LONG@{ub }. However, each has a set of E (built-in) functions
|
|
which safely manipulate variables of these types without exceeding their
|
|
bounds.
|
|
|
|
|
|
@{" Normal strings and E-strings " Link "Normal strings and E-strings" }
|
|
@{" String functions " Link "String functions" }
|
|
@{" Lists and E-lists " Link "Lists and E-lists" }
|
|
@{" List functions " Link "List functions" }
|
|
@{" Complex types " Link "Complex types" }
|
|
@{" Typed lists " Link "Typed lists" }
|
|
@{" Static data " Link "Static data" }
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Normal strings and E-strings" "Normal strings and E-strings"
|
|
@Next "String functions"
|
|
@Toc "LIST and STRING Types"
|
|
|
|
Normal strings and E-strings
|
|
----------------------------
|
|
|
|
@{fg shine }Normal@{fg text } strings are common to most programming languages. They are
|
|
simply an array of characters, with the end of the string marked by a null
|
|
character (ASCII zero). We've already met normal strings (see @{"Strings" Link "Introduction.guide/Strings" }).
|
|
The ones we used were constant strings contained in ' characters, and they
|
|
denote pointers to the memory where the string data is stored. Therefore,
|
|
you can assign a string constant to a pointer (to @{b }CHAR@{ub }), and you've got a
|
|
ready-filled array with the elements you want (an @{fg shine }initialised@{fg text } array.).
|
|
|
|
DEF s:PTR TO CHAR
|
|
s:='This is a string constant'
|
|
/* Now s[] is T and s[2] is i */
|
|
|
|
Remember that @{b }LONG@{ub } is actually @{b }PTR TO CHAR@{ub } so this code is precisely the
|
|
same as:
|
|
|
|
DEF s
|
|
s:='This is a string constant'
|
|
|
|
The following diagram illustrates the above assignment to @{b }s@{ub }. The first
|
|
two characters @{b }s[0]@{ub } and @{b }s[1]@{ub }) are @{b }T@{ub } and @{b }h@{ub }, and the last character (before
|
|
the terminating null) is @{b }t@{ub }. Memory marked as `Unknown' is not part of the
|
|
string constant.
|
|
|
|
+--------+
|
|
|Variable|
|
|
| 's' |
|
|
|--------|
|
|
|Address +----*
|
|
+--------+ \\
|
|
\\
|
|
\\
|
|
+-------+ +--\\----+ +-------+ +-------+ +-------+ +-------+
|
|
|Unknown| | s[0] | | s[1] | | s[24] | | s[25] | |Unknown|
|
|
Memory: |+-----+| |+-----+| |+-----+|...|+-----+| |+-----+| |+-----+|
|
|
|| XXX || || "T" || || "h" || || "t" || || 0 || || XXX ||
|
|
+=======+ +=======+ +=======+ +=======+ +=======+ +=======+
|
|
|
|
@{fg shine }E-strings@{fg text } are very similar to normal strings and, in fact, an
|
|
E-string can be used wherever a normal string can. However, the reverse
|
|
is not true, so if something requires an E-string you cannot use a normal
|
|
string instead. The difference between a normal string and an E-string
|
|
was hinted at in the introduction to this section: E-strings can be safely
|
|
altered without exceeding their bounds. A normal string is just an array
|
|
so you need to be careful not to exceed its bounds. However, an E-string
|
|
knows what its bounds are, and so any of the string manipulation functions
|
|
can alter them safely.
|
|
|
|
An E-string (@{b }STRING@{ub } type) variable is declared as in the following
|
|
example, with the maximum size of the E-string given just like an array
|
|
declaration.
|
|
|
|
DEF s[30]:STRING
|
|
|
|
As with an array declaration, the variable @{b }s@{ub } is actually a pointer to the
|
|
string data. To initialise an E-string you need to use the function
|
|
@{b }StrCopy@{ub } as we shall see.
|
|
|
|
There are some worked examples in Part Three (see
|
|
@{"String Handling and I-O" Link "Examples.guide/String Handling and I-O" }) which show how to use normal strings and
|
|
E-strings.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "String functions" "String functions"
|
|
@Next "Lists and E-lists"
|
|
@Prev "Normal strings and E-strings"
|
|
@Toc "LIST and STRING Types"
|
|
|
|
String functions
|
|
----------------
|
|
|
|
There are a number of useful built-in functions which manipulate
|
|
strings. Remember that an E-string can be used wherever a normal string
|
|
can, but normal strings cannot be used where an E-string is required. If
|
|
a parameter is marked as @{fg shine }string@{fg text } then a normal or E-string can be passed
|
|
as that parameter, but if it is marked as @{fg shine }e-string@{fg text } then only an E-string
|
|
may be used. Some of these functions have default arguments, which means
|
|
you don't need to specify some parameters to get the default values (see
|
|
@{"Default Arguments" Link "Procedures.guide/Default Arguments" }). (You can, of course, ignore the defaults and always
|
|
give all parameters.)
|
|
|
|
@{b }String(@{ub }@{fg shine }maxsize@{fg text }@{b })@{ub }
|
|
Allocates memory for an E-string of maximum size @{fg shine }maxsize@{fg text } and
|
|
returns a pointer to the string data. It is used to make space for a
|
|
new E-string, like a @{b }STRING@{ub } declaration does. The following code
|
|
fragments are practically equivalent:
|
|
|
|
DEF s[37]:STRING
|
|
|
|
DEF s:PTR TO CHAR
|
|
s:=String(37)
|
|
|
|
The slight difference is that there may not be enough memory left to
|
|
hold the E-string when the @{b }String@{ub } function is used. In that case the
|
|
special value @{b }NIL@{ub } (a constant) is returned. Your program @{i }must@{ui } check
|
|
that the value returned is not @{b }NIL@{ub } before you use it as an E-string
|
|
(or dereference it).
|
|
|
|
The memory for the declaration version, @{b }STRING@{ub }, is allocated when the
|
|
program is run, so your program won't run if there isn't enough
|
|
memory. The @{b }String@{ub } version is often called @{fg shine }dynamic@{fg text } allocation
|
|
because it happens only when the program is running; the declaration
|
|
version has allocation done by the E compiler.
|
|
|
|
The memory allocated using @{b }String@{ub } can be deallocated using
|
|
@{b }DisposeLink@{ub } (see @{"System support functions" Link "BuiltIns.guide/System support functions" }).
|
|
|
|
@{b }StrCmp(@{ub }@{fg shine }string1@{fg text }@{b },@{ub }@{fg shine }string2@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b }=ALL)@{ub }
|
|
Compares @{fg shine }string1@{fg text } with @{fg shine }string2@{fg text } (they can both be normal or
|
|
E-strings). Returns @{b }TRUE@{ub } if the first @{fg shine }length@{fg text } characters of the
|
|
strings match, and @{b }FALSE@{ub } otherwise. The @{fg shine }length@{fg text } defaults to the
|
|
special constant @{b }ALL@{ub } which means that the strings must agree on every
|
|
character. For example, the following comparisons all return @{b }TRUE@{ub }:
|
|
|
|
StrCmp('ABC', 'ABC')
|
|
StrCmp('ABC', 'ABC', ALL)
|
|
StrCmp('ABCd', 'ABC', 3)
|
|
StrCmp('ABCde','ABCxxjs',3)
|
|
|
|
And the following return @{b }FALSE@{ub } (notice the case of the letters):
|
|
|
|
StrCmp('ABC', 'ABc')
|
|
StrCmp('ABC', 'ABc', ALL)
|
|
StrCmp('ABCd', 'ABC', ALL)
|
|
|
|
@{b }StrCopy(@{ub }@{fg shine }e-string@{fg text }@{b },@{ub }@{fg shine }string@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b }=ALL)@{ub }
|
|
Copies the contents of @{fg shine }string@{fg text } to @{fg shine }e-string@{fg text }, and also returns a
|
|
pointer to the resulting E-string (for convenience). Only @{fg shine }length@{fg text }
|
|
characters are copied from the source string, but the special
|
|
constant @{b }ALL@{ub } can be used to indicate that the whole of the source
|
|
string is to be copied (and this is the default value for @{fg shine }length@{fg text }).
|
|
Remember that E-strings are safely manipulated, so the following code
|
|
fragment results in @{b }s@{ub } becoming @{b }More th@{ub }, since its maximum size is
|
|
(from its declaration) seven characters.
|
|
|
|
DEF s[7]:STRING
|
|
StrCopy(s, 'More than seven characters', ALL)
|
|
|
|
A declaration using @{b }STRING@{ub } (or @{b }ARRAY@{ub }) reserves a small part of
|
|
memory, and stores a pointer to this memory in the variable being
|
|
declared. So to get data into this memory you need to copy it there,
|
|
using @{b }StrCopy@{ub }. If you're familiar with very high-level languages
|
|
like BASIC you should take care, because you might think you can
|
|
assign a string to an array or an E-string variable. In E (and
|
|
languages like C and Assembly) you must explicitly copy data into
|
|
arrays and E-strings. You should not do the following:
|
|
|
|
/* You don't want to do things like this! */
|
|
DEF s[80]:STRING
|
|
s:='This is a string constant'
|
|
|
|
This is fairly disastrous: it throws away the pointer to reserved
|
|
memory that was stored in @{b }s@{ub } and replaces it by a pointer to the
|
|
string constant. @{b }s@{ub } is then no longer an E-string, and @{i }cannot@{ui } be
|
|
repaired using @{b }SetStr@{ub }. If you want @{b }s@{ub } to contain the above string you
|
|
must use @{b }StrCopy@{ub }:
|
|
|
|
DEF s[80]:STRING
|
|
StrCopy(s,'This is a string constant')
|
|
|
|
The moral is: remember when you are using pointers to data and when
|
|
you need to copy data. Also, remember that assignment does not copy
|
|
large arrays of data, it copies only pointers to data, so if you want
|
|
to store some data in an @{b }ARRAY@{ub } or @{b }STRING@{ub } type variable you need to
|
|
copy it there.
|
|
|
|
@{b }StrAdd(@{ub }@{fg shine }e-string@{fg text }@{b },@{ub }@{fg shine }string@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b }=ALL)@{ub }
|
|
This does the same as @{b }StrCopy@{ub } but the source string is copied onto
|
|
the end of the destination E-string. The following code fragment
|
|
results in @{b }s@{ub } becoming @{b }This is a string and a half@{ub }.
|
|
|
|
DEF s[30]:STRING
|
|
StrCopy(s, 'This is a string', ALL)
|
|
StrAdd(s, ' and a half')
|
|
|
|
@{b }StrLen(@{ub }@{fg shine }string@{fg text }@{b })@{ub }
|
|
Returns the length of @{fg shine }string@{fg text }. This assumes that the string is
|
|
terminated by a null character (i.e., ASCII zero), which is true for
|
|
any strings made from E-strings and string constants. However, you
|
|
can make a string constant look short if you use the null character
|
|
(the special sequence @{b }\\0@{ub }) in it. For instance, these calls all
|
|
return three:
|
|
|
|
StrLen('abc')
|
|
StrLen('abc\\0def')
|
|
|
|
In fact, most of the string functions assume strings are
|
|
null-terminated, so you shouldn't use null characters in your strings
|
|
unless you really know what you're doing.
|
|
|
|
For E-strings @{b }StrLen@{ub } is less efficient than the @{b }EstrLen@{ub } function.
|
|
|
|
@{b }EstrLen(@{ub }@{fg shine }e-string@{fg text }@{b })@{ub }
|
|
Returns the length of @{fg shine }e-string@{fg text } (remember this can be only an
|
|
E-string). This is much more efficient than @{b }StrLen@{ub } since E-strings
|
|
know their length and it doesn't need to search the string for a null
|
|
character.
|
|
|
|
@{b }StrMax(@{ub }@{fg shine }e-string@{fg text }@{b })@{ub }
|
|
Returns the maximum length of @{fg shine }e-string@{fg text }. This is not necessarily
|
|
the current length of the E-string, rather it is the size used in the
|
|
declaration with @{b }STRING@{ub } or the call to @{b }String@{ub }.
|
|
|
|
@{b }RightStr(@{ub }@{fg shine }e-string1@{fg text }@{b },@{ub }@{fg shine }e-string2@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b })@{ub }
|
|
This is like @{b }StrCopy@{ub } but it copies the right-most characters from
|
|
@{fg shine }e-string2@{fg text } to @{fg shine }e-string1@{fg text } and both strings must be E-strings. At
|
|
most @{fg shine }length@{fg text } characters are copied, and the special constant @{b }ALL@{ub }
|
|
@{i }cannot@{ui } be used (to copy all the string you should, of course, use
|
|
@{b }StrCopy@{ub }). For instance, a value of one for @{fg shine }length@{fg text } means the last
|
|
character of @{fg shine }e-string2@{fg text } is copied to @{fg shine }e-string1@{fg text }.
|
|
|
|
@{b }MidStr(@{ub }@{fg shine }e-string@{fg text }@{b },@{ub }@{fg shine }string@{fg text }@{b },@{ub }@{fg shine }index@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b }=ALL)@{ub }
|
|
Copies the contents of @{fg shine }string@{fg text } starting at @{fg shine }index@{fg text } (which is an
|
|
index just like an array index) to @{fg shine }e-string@{fg text }. At most @{fg shine }length@{fg text }
|
|
characters are copied, and the special constant @{b }ALL@{ub } can be used if
|
|
all the remaining characters in @{fg shine }string@{fg text } should be copied (this is
|
|
the default value for @{fg shine }length@{fg text }). For example, the following two
|
|
calls to @{b }MidStr@{ub } result in @{b }s@{ub } becoming @{b }four@{ub }:
|
|
|
|
DEF s[30]:STRING
|
|
MidStr(s, 'Just four', 5)
|
|
MidStr(s, 'Just four apples', 5, 4)
|
|
|
|
@{b }InStr(@{ub }@{fg shine }string1@{fg text }@{b },@{ub }@{fg shine }string2@{fg text }@{b },@{ub }@{fg shine }startindex@{fg text }@{b }=0)@{ub }
|
|
Returns the index of the first occurrence of @{fg shine }string2@{fg text } in @{fg shine }string1@{fg text }
|
|
starting at @{fg shine }startindex@{fg text } (in @{fg shine }string1@{fg text }). @{fg shine }startindex@{fg text } defaults to
|
|
zero. If @{fg shine }string2@{fg text } could not be found then -1 is returned.
|
|
|
|
@{b }TrimStr(@{ub }@{fg shine }string@{fg text }@{b })@{ub }
|
|
Returns the address of (i.e., a pointer to) the first non-whitespace
|
|
character in @{fg shine }string@{fg text }. For instance, the following code fragment
|
|
results in @{b }s@{ub } becoming @{b }12345@{ub }.
|
|
|
|
DEF s:PTR TO CHAR
|
|
s:=TrimStr(' \\n \\t 12345')
|
|
|
|
@{b }LowerStr(@{ub }@{fg shine }string@{fg text }@{b })@{ub }
|
|
Converts all uppercase letters in @{fg shine }string@{fg text } to lowercase. This change
|
|
is made @{fg shine }in-place@{fg text }, i.e., the contents of the string are directly
|
|
affected. The string is returned for convenience.
|
|
|
|
@{b }UpperStr(@{ub }@{fg shine }string@{fg text }@{b })@{ub }
|
|
Converts all lowercase letters in @{fg shine }string@{fg text } to uppercase. Again, this
|
|
change is made in-place and the string is returned for convenience.
|
|
|
|
@{b }SetStr(@{ub }@{fg shine }e-string@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b })@{ub }
|
|
Sets the length of @{fg shine }e-string@{fg text } to @{fg shine }length@{fg text }. E-strings know how long
|
|
they are, so if you alter an E-string (without using an E-string
|
|
function) and change its size you need to set its length using this
|
|
function before you can use it as an E-string again. For instance,
|
|
if you've used an E-string like an array (which you can do) and
|
|
written characters to it directly you must set its length before you
|
|
can treat it as anything other than an array:
|
|
|
|
DEF s[10]:STRING
|
|
s[0]:="a" /* Remember that "a" is a character value. */
|
|
s[1]:="b"
|
|
s[2]:="c"
|
|
s[3]:="d" /* At this point s is just an array of CHAR. */
|
|
SetStr(s, 4) /* Now, s can be used as an E-string again. */
|
|
SetStr(s, 2) /* s is a bit shorter, but still an E-string.*/
|
|
|
|
Notice that this function can be used to shorten an E-string, but
|
|
this change is @{fg shine }destructive@{fg text } (it cannot easily be reversed to give
|
|
the original, longer E-string).
|
|
|
|
@{b }Val(@{ub }@{fg shine }string@{fg text }@{b },@{ub }@{fg shine }address@{fg text }@{b }=NIL)@{ub }
|
|
What this function does is straight-forward but how you use it is a
|
|
bit complicated. Basically, it converts @{fg shine }string@{fg text } to a @{b }LONG@{ub } integer.
|
|
Leading whitespace is ignored, and a leading @{b }%@{ub } or @{b }$@{ub } means that the
|
|
string denotes a binary or hexadecimal integer (in the same way they
|
|
do for numeric constants). The decoded integer is returned as the
|
|
regular return value (see @{"Multiple Return Values" Link "Procedures.guide/Multiple Return Values" }). The number of
|
|
characters of @{fg shine }string@{fg text } that were read to make the integer is stored
|
|
at @{fg shine }address@{fg text }, which is usually a variable address (from using
|
|
@{b }{@{ub }@{fg shine }var@{fg text } @{b } }@{ub }), and is also returned as the
|
|
first optional return value. If @{fg shine }address@{fg text } is the special constant
|
|
@{b }NIL@{ub } (i.e., zero) then this number is not stored (this is the
|
|
default value for @{fg shine }address@{fg text }). You can use this number to calculate
|
|
the position in the string which was not part of the integer. If an
|
|
integer could not be decoded from the string then zero is returned as
|
|
both return values and stored at @{fg shine }address@{fg text }.
|
|
|
|
Follow the comments in this example, and pay special attention to the
|
|
use of the pointer @{b }p@{ub }.
|
|
|
|
DEF s[30]:STRING, value, chars, p:PTR TO CHAR
|
|
StrCopy(s, ' \\t \\n 10 \\t $3F -%0101010')
|
|
value, chars:=Val('abcde 10 20') -> Two return values...
|
|
/* After the above line, value and chars will both be zero */
|
|
value:=Val(s, {chars}) -> Use address of chars
|
|
/* Now value will be 10, chars will be 7 */
|
|
p:=s+chars
|
|
/* p now points to the space after the 10 in s */
|
|
value, chars:=Val(p)
|
|
/* Now value will be $3F (63), chars will be 6 */
|
|
p:=p+chars
|
|
/* p now points to the space after the $3F in s */
|
|
value, chars:=Val(p)
|
|
/* Now value will be -%0101010 (-42), chars will be 10 */
|
|
|
|
Notice the two different ways of finding the number of characters
|
|
read: a multiple-assignment and using the address of a variable.
|
|
|
|
There's a couple of other string functions (@{b }ReadStr@{ub } and @{b }StringF@{ub }) which
|
|
will be discussed later (see @{"Input and output functions" Link "BuiltIns.guide/Input and output functions" }).
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Lists and E-lists" "Lists and E-lists"
|
|
@Next "List functions"
|
|
@Prev "String functions"
|
|
@Toc "LIST and STRING Types"
|
|
|
|
Lists and E-lists
|
|
-----------------
|
|
|
|
Lists are just like strings with @{b }LONG@{ub } elements rather than @{b }CHAR@{ub }
|
|
elements (so they are very much like @{b }ARRAY OF LONG@{ub }). The list equivalent
|
|
of an E-string is something called an @{fg shine }E-list@{fg text }. It has the same
|
|
properties as an E-string, except the elements are @{b }LONG@{ub } (so could be
|
|
pointers). Normal lists are most like string constants, except that the
|
|
elements can be built from variables and so do not have to be constants.
|
|
Just as strings are not true E-strings, (normal) lists are not true
|
|
E-lists.
|
|
|
|
Lists are written using @{b }[@{ub } and @{b }]@{ub } to delimit comma separated elements.
|
|
Like string constants a list returns the address of the memory which
|
|
contains the elements.
|
|
|
|
For example the following code fragment:
|
|
|
|
DEF list:PTR TO LONG, number
|
|
number:=22
|
|
list:=[1,2,3,number]
|
|
|
|
is equivalent to:
|
|
|
|
DEF list[4]:ARRAY OF LONG, number
|
|
number:=22
|
|
list[0]:=1
|
|
list[1]:=2
|
|
list[2]:=3
|
|
list[3]:=number
|
|
|
|
Now, which of these two versions would you rather write? As you can see,
|
|
lists are pretty useful for making your program easier to write and much
|
|
easier to read.
|
|
|
|
E-list variables are like E-string variables and are declared in much
|
|
the same way. The following code fragment declares @{b }lt@{ub } to be an E-list of
|
|
maximum size 30. As ever, @{b }lt@{ub } is then a pointer (to @{b }LONG@{ub }), and it points
|
|
to the memory allocated by the declaration.
|
|
|
|
DEF lt[30]:LIST
|
|
|
|
Lists are most useful for writing @{fg shine }tag lists@{fg text }, which are increasingly
|
|
used in important Amiga system functions. A tag list is a list where the
|
|
elements are thought of in pairs. The first element of a pair is the tag,
|
|
and the second is some data for that tag. See the `Rom Kernel Reference
|
|
Manual (Libraries)' for more details.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "List functions" "List functions"
|
|
@Next "Complex types"
|
|
@Prev "Lists and E-lists"
|
|
@Toc "LIST and STRING Types"
|
|
|
|
List functions
|
|
--------------
|
|
|
|
There are a number of list functions which are very similar to the
|
|
string functions (see @{"String functions" Link "String functions" }). Remember that E-lists are the
|
|
list equivalents of E-strings, i.e., they can be altered and extended
|
|
safely without exceeding their bounds. As with E-strings, E-lists are
|
|
downwardly compatible with lists. Therefore, if a function requires a
|
|
list as a parameter you can supply a list or an E-list. But if a function
|
|
requires an E-list you cannot use a list in its place.
|
|
|
|
@{b }List(@{ub }@{fg shine }maxsize@{fg text }@{b })@{ub }
|
|
Allocates memory for an E-list of maximum size @{fg shine }maxsize@{fg text } and returns
|
|
a pointer to the list data. It is used to make space for a new
|
|
E-list, like a @{b }LIST@{ub } declaration does. The following code fragments
|
|
are (as with @{b }String@{ub }) practically equivalent:
|
|
|
|
DEF lt[46]:LIST
|
|
|
|
DEF lt:PTR TO LONG
|
|
lt:=List(46)
|
|
|
|
You need to check that the return value from @{b }List@{ub } is not @{b }NIL@{ub } before
|
|
you use it as an E-list. Like @{b }String@{ub }, the memory allocated using
|
|
@{b }List@{ub } is deallocated using @{b }DisposeLink@{ub } (see
|
|
@{"System support functions" Link "BuiltIns.guide/System support functions" }).
|
|
|
|
@{b }ListCmp(@{ub }@{fg shine }list1@{fg text }@{b },@{ub }@{fg shine }list2@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b }=ALL)@{ub }
|
|
Compares @{fg shine }list1@{fg text } with @{fg shine }list2@{fg text } (they can both be normal or E-lists).
|
|
Works just like @{b }StrCmp@{ub } does for E-strings, so, for example, the
|
|
following comparisons all return @{b }TRUE@{ub }:
|
|
|
|
ListCmp([1,2,3,4], [1,2,3,4])
|
|
ListCmp([1,2,3,4], [1,2,3,7], 3)
|
|
ListCmp([1,2,3,4,5], [1,2,3], 3)
|
|
|
|
@{b }ListCopy(@{ub }@{fg shine }e-list@{fg text }@{b },@{ub }@{fg shine }list@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b }=ALL)@{ub }
|
|
Works just like @{b }StrCopy@{ub }, and the following example shows how to
|
|
initialise an E-list:
|
|
|
|
DEF lt[7]:LIST, x
|
|
x:=4
|
|
ListCopy(lt, [1,2,3,x])
|
|
|
|
As with @{b }StrCopy@{ub }, an E-list cannot be over-filled using @{b }ListCopy@{ub }.
|
|
|
|
@{b }ListAdd(@{ub }@{fg shine }e-list@{fg text }@{b },@{ub }@{fg shine }list@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b }=ALL)@{ub }
|
|
Works just like @{b }StrAdd@{ub }, so this next code fragment results in the
|
|
E-list @{b }lt@{ub } becoming the E-list version of @{b }[1,2,3,4,5,6,7,8]@{ub }.
|
|
|
|
DEF lt[30]:LIST
|
|
ListCopy(lt, [1,2,3,4])
|
|
ListAdd(lt, [5,6,7,8])
|
|
|
|
@{b }ListLen(@{ub }@{fg shine }list@{fg text }@{b })@{ub }
|
|
Works just like @{b }StrLen@{ub }, returning the length of @{fg shine }list@{fg text }. There is no
|
|
E-list specific length function.
|
|
|
|
@{b }ListMax(@{ub }@{fg shine }e-list@{fg text }@{b })@{ub }
|
|
Works just like @{b }StrMax@{ub }, returning the maximum length of the @{fg shine }e-list@{fg text }.
|
|
|
|
@{b }SetList(@{ub }@{fg shine }e-list@{fg text }@{b },@{ub }@{fg shine }length@{fg text }@{b })@{ub }
|
|
Works just like @{b }SetStr@{ub }, setting the length of @{fg shine }e-list@{fg text } to @{fg shine }length@{fg text }.
|
|
|
|
@{b }ListItem(@{ub }@{fg shine }list@{fg text }@{b },@{ub }@{fg shine }index@{fg text }@{b })@{ub }
|
|
Returns the element of @{fg shine }list@{fg text } at @{fg shine }index@{fg text }. For example, if @{b }lt@{ub } is an
|
|
E-list (so a @{b }PTR TO LONG@{ub }) then @{b }ListItem(lt,n)@{ub } is the same as @{b }lt[n]@{ub }.
|
|
This function is most useful when the list is not an E-list; for
|
|
example, the following two code fragments are equivalent:
|
|
|
|
WriteF(ListItem(['Fred','Barney','Wilma','Betty'], name))
|
|
|
|
DEF lt:PTR TO LONG
|
|
lt:=['Fred','Barney','Wilma','Betty']
|
|
WriteF(lt[name])
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Complex types" "Complex types"
|
|
@Next "Typed lists"
|
|
@Prev "List functions"
|
|
@Toc "LIST and STRING Types"
|
|
|
|
Complex types
|
|
-------------
|
|
|
|
In E the @{b }STRING@{ub } and @{b }LIST@{ub } types are called @{fg shine }complex@{fg text } types.
|
|
Complex-typed variables can also be created using the @{b }String@{ub } and @{b }List@{ub }
|
|
functions as we've seen in the previous sections.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Typed lists" "Typed lists"
|
|
@Next "Static data"
|
|
@Prev "Complex types"
|
|
@Toc "LIST and STRING Types"
|
|
|
|
Typed lists
|
|
-----------
|
|
|
|
Normal lists contain @{b }LONG@{ub } elements, so you can write initialised arrays
|
|
of @{b }LONG@{ub } elements. What about other kinds of array? Well, that's what
|
|
@{fg shine }typed@{fg text } lists are for. You specify the type of the elements of a list
|
|
using @{b } :@{ub }@{fg shine }type@{fg text }@{b }@{ub } after the closing @{b }]@{ub }. The allowable types are @{b }CHAR@{ub }, @{b }INT@{ub },
|
|
@{b }LONG@{ub } and any object type. There is a subtle difference between a normal,
|
|
@{b }LONG@{ub } list and a typed list (even a @{b }LONG@{ub } typed list): only normal lists can
|
|
be used with the list functions (see @{"List functions" Link "List functions" }). For this reason,
|
|
the term `list' tends to refer only to normal lists.
|
|
|
|
The following code fragment uses the object @{b }rec@{ub } defined earlier (see
|
|
@{"Example object" Link "Example object" }) and gives a couple of examples of typed lists:
|
|
|
|
DEF ints:PTR TO INT, objects:PTR TO rec, p:PTR TO CHAR
|
|
ints:=[1,2,3,4]:INT
|
|
p:='fred'
|
|
objects:=[1,2,p,4,
|
|
300,301,'barney',303]:rec
|
|
|
|
It is equivalent to:
|
|
|
|
DEF ints[4]:ARRAY OF INT, objects[2]:ARRAY OF rec, p:PTR TO CHAR
|
|
ints[0]:=1
|
|
ints[1]:=2
|
|
ints[2]:=3
|
|
ints[3]:=4
|
|
p:='fred'
|
|
objects[0].tag:=1
|
|
objects[0].check:=2
|
|
objects[0].table:=p
|
|
objects[0].data:=4
|
|
objects[1].table:='barney'
|
|
objects[1].tag:=300
|
|
objects[1].data:=303
|
|
objects[1].check:=301
|
|
|
|
The last group of assignments to @{b }objects[1]@{ub } have deliberately been
|
|
shuffled in order to emphasise that the order of the elements in the
|
|
@{i }definition@{ui } of the object @{b }rec@{ub } is significant. Each of the elements of the
|
|
list corresponds to an element in the object, and the order of elements in
|
|
the list corresponds to the order in the object definition. In the
|
|
example, the (object) list assignment line was broken after the end of the
|
|
first object (the fourth element) to make it a bit more readable.
|
|
|
|
The last object in the list need not be completely defined, so, for
|
|
instance, the second line of the assignment could have contained only
|
|
three elements. This makes an object-typed list slightly different from
|
|
the corresponding array of objects, since an array always defines a whole
|
|
number of objects. With an object-typed list you must be careful not to
|
|
access the undefined elements of a partially defined, trailing object.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Static data" "Static data"
|
|
@Prev "Typed lists"
|
|
@Toc "LIST and STRING Types"
|
|
|
|
Static data
|
|
-----------
|
|
|
|
String constants (e.g., @{b }fred@{ub }), lists (e.g., @{b }[1,2,3]@{ub }) and typed lists
|
|
(e.g., @{b }[1,2,3]:INT@{ub }) are @{fg shine }static@{fg text } data. This means that the address of the
|
|
(initialised) data is fixed when the program is run. Normally you don't
|
|
need to worry about this, but, for instance, if you want to have a series
|
|
of lists as initialised arrays you might be tempted to use some kind of
|
|
loop:
|
|
|
|
PROC main()
|
|
DEF i, a[10]:ARRAY OF LONG, p:PTR TO LONG
|
|
FOR i:=0 TO 9
|
|
a[i]:=[1, i, i*i]
|
|
/* This assignment is probably not what you want! */
|
|
ENDFOR
|
|
FOR i:=0 TO 9
|
|
p:=a[i]
|
|
WriteF('a[\\d] is an array at address \\d\\n', i, p)
|
|
WriteF(' and the second element is \\d\\n', p[1])
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
The array @{b }a@{ub } is an array of pointers to initialised arrays (which are all
|
|
three elements long). But, as the comment suggests and the program shows,
|
|
this probably doesn't do what was intended, since the list is static.
|
|
That means the address of the list is fixed, so each element of @{b }a@{ub } gets the
|
|
same address (i.e., the same array). Since @{b }i@{ub } is used in the list, the
|
|
contents of that part of memory varies slightly as the first @{b }FOR@{ub } loop is
|
|
processed. But after this loop the contents remain fixed, and the second
|
|
element of each of the ten arrays is always nine. This is an example of
|
|
the output that will be generated (the @{b }...@{ub } represents a number of similar
|
|
lines):
|
|
|
|
a[0] is an array at address 4021144
|
|
and the second element is 9
|
|
a[1] is an array at address 4021144
|
|
and the second element is 9
|
|
...
|
|
a[9] is an array at address 4021144
|
|
and the second element is 9
|
|
|
|
One solution is to use the dynamic typed-allocation operator @{b }NEW@{ub } (see
|
|
@{"NEW and END Operators" Link "Memory.guide/NEW and END Operators" }). Another solution is to use the function @{b }List@{ub } and
|
|
copy the normal list into the new E-list using @{b }ListCopy@{ub }:
|
|
|
|
PROC main()
|
|
DEF i, a[10]:ARRAY OF LONG, p:PTR TO LONG
|
|
FOR i:=0 TO 9
|
|
a[i]:=List(3)
|
|
/* Must check that the allocation succeeded before copying */
|
|
IF a[i]<>NIL THEN ListCopy(a[i], [1, i, i*i], ALL)
|
|
ENDFOR
|
|
FOR i:=0 TO 9
|
|
p:=a[i]
|
|
IF p=NIL
|
|
WriteF('Could not allocate memory for a[\\d]\\n', i)
|
|
ELSE
|
|
WriteF('a[\\d] is an array at address \\d\\n', i, p)
|
|
WriteF(' and the second element is \\d\\n', p[1])
|
|
ENDIF
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
The problem is not so bad with string constants, since the contents are
|
|
fixed. However, if you alter the contents explicitly, you will need to
|
|
take care not to run into the same problem, as this next example shows.
|
|
|
|
PROC main()
|
|
DEF i, strings[10]:ARRAY OF LONG, s:PTR TO CHAR
|
|
FOR i:=0 TO 9
|
|
strings[i]:='Hello World\\n'
|
|
/* This assignment is probably not what you want! */
|
|
ENDFOR
|
|
s:=strings[4]
|
|
s[5]:="X"
|
|
FOR i:=0 TO 9
|
|
WriteF('strings[\\d] is ', i)
|
|
WriteF(strings[i])
|
|
ENDFOR
|
|
ENDPROC
|
|
|
|
This is an example of the output that will be generated (again, the @{b }...@{ub }
|
|
represents a number of similar lines)::
|
|
|
|
strings[0] is HelloXWorld
|
|
strings[1] is HelloXWorld
|
|
...
|
|
strings[9] is HelloXWorld
|
|
|
|
The solution, once more, is to use dynamic allocation. The functions
|
|
@{b }String@{ub } and @{b }StrCopy@{ub } should be used in the same way that @{b }List@{ub } and @{b }ListCopy@{ub }
|
|
were used above.
|
|
|
|
|
|
@ENDNODE
|
|
|
|
@NODE "Linked Lists" "Linked Lists"
|
|
@Prev "LIST and STRING Types"
|
|
@Toc "main"
|
|
|
|
Linked Lists
|
|
============
|
|
|
|
E-lists and E-strings have a useful extension: they can be used to make
|
|
@{fg shine }linked lists@{fg text }. These are like the lists we've seen already, except the
|
|
list elements do not occupy a contiguous block of memory. Instead, each
|
|
element has an extra piece of data: a pointer to the next element in the
|
|
list. This means that each element can be anywhere in memory. (Normally,
|
|
the next element of a list is in the next position in memory.) The end of
|
|
a linked list has been reached when the pointer to the next element is the
|
|
special value @{b }NIL@{ub } (a constant representing zero). You need to be very
|
|
careful to check that the pointer is not @{b }NIL@{ub }, or else strange things will
|
|
happen to your program...
|
|
|
|
The elements of a linked list are E-lists or E-strings (i.e., the
|
|
elements are complex typed). So, you can link E-lists to get a `linked
|
|
list of E-lists' (or, more simply, a `list of lists'). Similarly, linking
|
|
E-strings gives `linked list of E-strings', or a `list of strings'. You
|
|
don't have to stick to these two kinds of linked lists, though: you can
|
|
use a mixture of E-lists and E-strings in the same linked list. To link
|
|
one complex typed element to another you use the @{b }Link@{ub } function and to find
|
|
subsequent elements in a linked list you use the @{b }Next@{ub } or @{b }Forward@{ub } functions.
|
|
|
|
@{b }Link(@{ub }@{fg shine }complex1@{fg text }@{b },@{ub }@{fg shine }complex2@{fg text }@{b })@{ub }
|
|
Links @{fg shine }complex1@{fg text } to @{fg shine }complex2@{fg text }. Both must be an E-list or an
|
|
E-string, with the exception that @{fg shine }complex2@{fg text } can be the special
|
|
constant @{b }NIL@{ub } to indicate that @{fg shine }complex1@{fg text } is the end of the linked
|
|
list. The value @{fg shine }complex1@{fg text } is returned by the function, which isn't
|
|
always useful so, usually, calls to @{b }Link@{ub } will be used as statements
|
|
rather than functions.
|
|
|
|
The effect of @{b }Link@{ub } is that @{fg shine }complex1@{fg text } will point to @{fg shine }complex2@{fg text } as the
|
|
next element in the linked list (so @{fg shine }complex1@{fg text } is the @{fg shine }head@{fg text } of the
|
|
list, and @{fg shine }complex2@{fg text } is the @{fg shine }tail@{fg text }). For both E-lists and E-strings
|
|
the pointer to the next element is initially @{b }NIL@{ub }, so you will only
|
|
need to use @{b }Link@{ub } with a @{b }NIL@{ub } parameter when you want to make a linked
|
|
list shorter (by losing the tail).
|
|
|
|
@{b }Next(@{ub }@{fg shine }complex@{fg text }@{b })@{ub }
|
|
Returns the pointer to the next element in the linked list. This may
|
|
be the special constant @{b }NIL@{ub } if @{fg shine }complex@{fg text } is the last element in the
|
|
linked list. Be careful to check that the value isn't @{b }NIL@{ub } before you
|
|
dereference it! Follow the comments in the example below:
|
|
|
|
DEF s[23]:STRING, t[7]:STRING, lt[41]:LIST, lnk
|
|
/* The next two lines set up the linked list "lnk" */
|
|
lnk:=Link(lt,t) /* lnk list starts at lt and is lt->t */
|
|
lnk:=Link(s,lt) /* Now it starts at s and is s->lt->t */
|
|
/* The next three lines follow the links in "lnk" */
|
|
lnk:=Next(lnk) /* Now it starts at lt and is lt->t */
|
|
lnk:=Next(lnk) /* Now it starts at t and is t */
|
|
lnk:=Next(lnk) /* Now lnk is NIL so the list has ended */
|
|
|
|
You may safely call @{b }Next@{ub } with a @{b }NIL@{ub } parameter, and in this case it
|
|
will return @{b }NIL@{ub }.
|
|
|
|
@{b }Forward(@{ub }@{fg shine }complex@{fg text }@{b },@{ub }@{fg shine }expression@{fg text }@{b })@{ub }
|
|
Returns a pointer to the element which is @{fg shine }expression@{fg text } number of
|
|
links down the linked list @{fg shine }complex@{fg text }. If @{fg shine }expression@{fg text } is one, then a
|
|
pointer to the next element is returned (just like using @{b }Next@{ub }). If
|
|
it's two a pointer to the element after that is returned, and so on.
|
|
|
|
If @{fg shine }expression@{fg text } is greater than the number of links in the list the
|
|
special value @{b }NIL@{ub } is returned.
|
|
|
|
Since the link in a linked list is a pointer to the next element you
|
|
can look through the list only from beginning to end. Technically this is
|
|
a @{fg shine }singly@{fg text } linked list (a @{fg shine }doubly@{fg text } linked list would also have a pointer
|
|
to the previous element in the list, enabling backwards searching through
|
|
the list).
|
|
|
|
Linked lists are useful for building lists that can grow quite large.
|
|
This is because it's much better to have lots of small bits of memory than
|
|
a large lump. However, you need only worry about these things when you're
|
|
playing with quite big lists (as a rough guide, ones with over 100,000
|
|
elements are big!).
|
|
|
|
|
|
@ENDNODE
|
|
|