Table of Contents
User-Defined Types
User-defined types (or UDT's) allow you to create your own data structures. In its simplest form, a type definition is a bunch of field definitions inside a TYPE … END TYPE
block. The following example illustrates a very simple type definition:
TYPE EMPLOYEE firstname AS STRING * 16 lastname AS STRING * 16 salary AS LONG END TYPE
A type can have any number of fields, but there is one restriction: the size of a single instance of the type may not exceed 64 bytes. In the above example, firstname and lastname take up 17 bytes each, and salary, being of LONG type takes 3 bytes. That's 37 bytes total, well below the limit.
Having defined the new type as above, you can define variables of EMPLOYEE type and assign values to its fields using the dot (.
) notation:
DIM emp AS EMPLOYEE emp.firstname = "Mark" emp.lastname = "Cunnigham" emp.salary = 45500
To read a field value you can use the same dot-notation:
PRINT emp.firstname : REM outputs: "Mark"
Note
Variables defined as UDT's are called instances of that type. In the above example, emp
is an instance of the EMPLOYEE type.
Arrays of user defined types variables
Multiple instances of UDT's can be organized in arrays, for example:
DIM employees(50) AS EMPLOYEE
Now to access field values of a specific member in the array, you can combine the array notation and the dot notation:
DIM sum AS LONG : sum = 0 FOR i AS BYTE = 0 TO 49 sum = sum + employees(i).salary NEXT i PRINT "the average salary at the company is "; sum / 50
Warning
Arrays can be dimensioned as user-defined type, however TYPE definitions cannot contain array fields.
Nested UDT's
A field in a TYPE definition can be of another TYPE, and so on, as long as a single insance does not exceed the 64 bytes limit explained above.
TYPE VECTOR x AS INT y AS INT END TYPE TYPE SPRITE pos AS VECTOR color AS BYTE END TYPE
To access fields within the nested type, you can extend the dot notation like in the following example:
DIM monster AS SPRITE monster.pos.x = 160 monster.pos.y = 100 monster.color = 3
Type methods
You can define Subroutines and Functions within a type declaration that allows you to define routines that work with data in a single instance of that type. These routines are called type methods and are very similar to object methods in object-oriented programming. Extending the example above, let's write a routine that moves the imaginary monster on the horizontal axis:
TYPE VECTOR x AS INT y AS INT END TYPE TYPE SPRITE pos AS VECTOR color AS BYTE SUB move (amount AS INT) STATIC THIS.pos.x = THIS.pos.x + amount END SUB END TYPE DIM monster AS SPRITE monster.pos.x = 100 CALL monster.move(5) PRINT monster.pos.x : REM outputs 105
The THIS keyword
Note the usage of the THIS keyword in the above example. THIS
is a special variable that refers to the instance on which the method was called. In the above example, THIS
refers to the variable that the move
method was called on, in this case: monster.
Note
Adding methods to a TYPE definition does not increase an instance's size and therefore methods do not count in the 64 bytes size limit.
Defining functions within type definitions allows you to write methods that return a value. The following example is a theoretical game where two players fight each other, hitting in rounds, with a random damage. The method hit(damage)
subtracts the damage from a fighter's energy and the method isdead()
tells if the fighter is out of energy.
TYPE FIGHTER energy as INT SUB hit (damage AS BYTE) STATIC THIS.energy = THIS.energy - CINT(damage) END SUB FUNCTION isdead AS BYTE () STATIC RETURN THIS.energy <= 0 END FUNCTION END TYPE DIM player1 AS FIGHTER DIM player2 AS FIGHTER player1.energy = 1000 player2.energy = 1000 RANDOMIZE TI() DO CALL player1.hit(CBYTE(RNDL())) CALL player2.hit(CBYTE(RNDL())) PRINT "p1 energy: "; player1.energy; ", p2 energy: "; player2.energy LOOP UNTIL player1.isdead() OR player2.isdead() PRINT "game over"
As seen above, you can organize code in a very well-structured way with the help of UDT's.