Dark Bit Factory & Gravity
PROGRAMMING => Coding tutorials => Topic started by: rdc on December 02, 2009
-
The Data Object
In order to make the data structures presented in these tutorials as general as possible, each of the data structures will use the data object presented here as the basic data repository. The data object can handle all of the basic data types available in FreeBasic, and is implemented as a FreeBasic object with a set of associated properties, subroutines and operators. Using a standard data object will make using the data structures presented in this wikibook able to handle any of the standard FreeBasic data types, and is useful as a standalone object in the case where a typeless data object might be used (for example, within a script language).
The complete object code is listed, followed by an in-depth examination of the code.
The Data Object Code
Filename: dataobj.bi
/'****************************************************************************
*
* Name: dataobj.bi
*
* Synopsis: Data object used in various data structures.
*
* Description: The data object is a variant data object that can hold one of
* the standard FreeBasic data types.
*
*
* Copyright 2009, Richard D. Clark
*
* The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies.
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************'/
'Create a NULL value.
#Ifndef NULL
#Define NULL 0
#EndIf
'Enumeration of allowable types in each data object.
Enum cdatatype
tNull
tByte
tUByte
tShort
tUShort
tInteger
tUInteger
tLong
tUlong
tLongint
tUlongint
tSingle
tDouble
tString
End Enum
'Data object type definition.
Type dataobj
Private:
_ctype As cdatatype 'Current type of data, default is null.
Union
_clbyte As Byte 'Depending on the value of ctype, _cl* will contain the data.
_clubyte As UByte 'If ctype is tNull, the object will not contain any valid data.
_clshort As Short
_clushort As UShort
_clinteger As Integer
_cluinteger As UInteger
_cllong As Long
_clulong As ULong
_cllongint As LongInt
_clulongint As ULongInt
_clsingle As Single
_cldouble As Double
_clstring As ZString Ptr
End Union
Declare Sub _ClearData ()
Public:
Declare Constructor ()
Declare Destructor ()
Declare Property DataType () As cdatatype 'Returns the object data type.
Declare Sub ClearData ()
Declare Operator Let (rhs As dataobj) 'Sets data using a dataobj object.
Declare Operator Let (rhs As Byte) 'Sets object data using variable.
Declare Operator Let (rhs As UByte)
Declare Operator Let (rhs As Short)
Declare Operator Let (rhs As UShort)
Declare Operator Let (rhs As Integer)
Declare Operator Let (rhs As UInteger)
Declare Operator Let (rhs As Long)
Declare Operator Let (rhs As ULong)
Declare Operator Let (rhs As LongInt)
Declare Operator Let (rhs As ULongInt)
Declare Operator Let (rhs As Single)
Declare Operator Let (rhs As Double)
Declare Operator Let (rhs As String)
Declare Operator Cast () As Byte 'Cast to a particular data type.
Declare Operator Cast () As UByte
Declare Operator Cast () As Short
Declare Operator Cast () As UShort
Declare Operator Cast () As Integer
Declare Operator Cast () As UInteger
Declare Operator Cast () As Long
Declare Operator Cast () As ULong
Declare Operator Cast () As LongInt
Declare Operator Cast () As ULongInt
Declare Operator Cast () As Single
Declare Operator Cast () As Double
Declare Operator Cast () As String
End Type
'Clears any data in object and sets to Null.
Sub dataobj._ClearData()
'Based on what the type is, clear the data.
Select Case _ctype
Case tNull
'Nothing to do here.
Case tByte
_clbyte = 0
Case tUByte
_clubyte = 0
Case tShort
_clshort = 0
Case tUShort
_clushort = 0
Case tInteger
_clinteger = 0
Case tUInteger
_cluinteger = 0
Case tLong
_cllong = 0
Case tUlong
_clulong = 0
Case tLongint
_cllongint = 0
Case tUlongint
_cllongint = 0
Case tSingle
_clsingle = 0
Case tDouble
_cldouble = 0
Case tString
'Deallocate string if present and set ptr to null.
If _clstring <> NULL Then
DeAllocate _clstring
_clstring = NULL
EndIf
End Select
'Set data type to Null.
_ctype = tNull
End Sub
'Object constructor sets object to Null.
Constructor dataobj ()
_ctype = tNull
End Constructor
'Calls _ClearData to clear contents.
Destructor dataobj ()
_ClearData
End Destructor
'Calls _ClearData to clear contents.
Sub dataobj.ClearData ()
_ClearData
End Sub
'Returns the object data type.
Property dataobj.DataType () As cdatatype
Return _ctype
End Property
'Set the value of the data object using another data object.
Operator dataobj.Let (rhs As dataobj)
'Clears any current data and set object to NULL.
_ClearData
'Based on what the passed type is, set the data.
_ctype = rhs.DataType
Select Case _ctype
Case tNull
'Nothing to do here.
Case tByte
_clbyte = rhs
Case tUByte
_clubyte = rhs
Case tShort
_clshort = rhs
Case tUShort
_clushort = rhs
Case tInteger
_clinteger = rhs
Case tUInteger
_cluinteger = rhs
Case tLong
_cllong = rhs
Case tUlong
_clulong = rhs
Case tLongint
_cllongint = rhs
Case tUlongint
_clulongint = rhs
Case tSingle
_clsingle = rhs
Case tDouble
_cldouble = rhs
Case tString 'If a string is passed, allocate space for new string, else set to NULL.
Dim As String s = rhs
Dim As Integer l = Len(s)
If l > 0 Then
_clstring = Allocate(l + 1)
*_clstring = s
Else
_clstring = NULL
EndIf
End Select
End Operator
'Sets object data using variable.
Operator dataobj.Let (rhs As Byte)
'Clear any existing data.
_ClearData
_ctype = tByte
_clbyte = rhs
End Operator
Operator dataobj.Let (rhs As UByte)
'Clear any existing data.
_ClearData
_ctype = tUByte
_clubyte = rhs
End Operator
Operator dataobj.Let (rhs As Short)
'Clear any existing data.
_ClearData
_ctype = tShort
_clshort = rhs
End Operator
Operator dataobj.Let (rhs As UShort)
'Clear any existing data.
_ClearData
_ctype = tUShort
_clushort = rhs
End Operator
Operator dataobj.Let (rhs As Integer)
'Clear any existing data.
_ClearData
_ctype = tInteger
_clinteger = rhs
End Operator
Operator dataobj.Let (rhs As UInteger)
'Clear any existing data.
_ClearData
_ctype = tUinteger
_cluinteger = rhs
End Operator
Operator dataobj.Let (rhs As Long)
'Clear any existing data.
_ClearData
_ctype = tLong
_cllong = rhs
End Operator
Operator dataobj.Let (rhs As ULong)
'Clear any existing data.
_ClearData
_ctype = tULong
_clulong = rhs
End Operator
Operator dataobj.Let (rhs As LongInt)
'Clear any existing data.
_ClearData
_ctype = tLongint
_cllongint = rhs
End Operator
Operator dataobj.Let (rhs As ULongInt)
'Clear any existing data.
_ClearData
_ctype = tULongint
_clulongint = rhs
End Operator
Operator dataobj.Let (rhs As Single)
'Clear any existing data.
_ClearData
_ctype = tSingle
_clsingle = rhs
End Operator
Operator dataobj.Let (rhs As Double)
'Clear any existing data.
_ClearData
_ctype = tDouble
_cldouble = rhs
End Operator
Operator dataobj.Let (rhs As String)
Dim As Integer l = Len(rhs)
'Clear any existing data.
_ClearData
'Akllocate spaxe for string, or NULL for empty string.
_ctype = tString
If l > 0 Then
_clstring = Allocate(l + 1)
*_clstring = rhs
Else
_clstring = NULL
EndIf
End Operator
'Cast to data type. Allows returning data into a simple variable of type.
Operator dataobj.Cast () As Byte
If _ctype = tByte Then
Return _clbyte
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As UByte
If _ctype = tuByte Then
Return _clubyte
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As Short
If _ctype = tShort Then
Return _clshort
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As UShort
If _ctype = tUShort Then
Return _clushort
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As Integer
If _ctype = tInteger Then
Return _clinteger
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As UInteger
If _ctype = tUInteger Then
Return _cluinteger
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As Long
If _ctype = tLong Then
Return _cllong
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As ULong
If _ctype = tULong Then
Return _clulong
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As LongInt
If _ctype = tLongInt Then
Return _cllongint
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As ULongInt
If _ctype = tUlongInt Then
Return _clulongint
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As Single
If _ctype = tSingle Then
Return _clsingle
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As Double
If _ctype = tDouble Then
Return _cldouble
Else
Return 0
EndIf
End Operator
Operator dataobj.Cast () As String
'Return string id present or empty string if NULL.
If _ctype = tString Then
If _clstring <> NULL Then
Return *_clstring
Else
Return ""
EndIf
EndIf
End Operator
Code Review
'Create a NULL value.
#Ifndef NULL
#Define NULL 0
#EndIf
In this section of the code, the special data type *NULL* is defined. Since NULL may be defined by other sections of code, or within a library, the NULL definition is wrapped within the preprocessor IF statement to prevent conflicts. NULL represents the uninitialized state of the data object and means that the data object does not contain a valid value. Whenever a data object is created or cleared, the value of the data object becomes NULL.
'Enumeration of allowable types in each data object.
Enum cdatatype
tNull
tByte
tUByte
tShort
tUShort
tInteger
tUInteger
tLong
tUlong
tLongint
tUlongint
tSingle
tDouble
tString
End Enum
This enumeration is used as the data type id and is stored in the private variable _ctype. The underscore is used as a reminder that this is a private variable and can only be accessed from within the type definition. To determine the current data type, use the public property DataType, which will return one of these enumerated values.
The object definition, the code delimited by the Type-End Type keywords, is contains both Private: and Public: members. The Private: members cannot be accessed from outside the object code, and is used to maintain data integrity within the object. The Public: members comprise the object's public interface and allows a program to interact with the object. Any Private: data that is stored or retrieved from the data object must pass through the Public: interface. This ensures that the integrity of the data is maintained for the life of the object.
Private Members
Private:
_ctype As cdatatype 'Current type of data, default is null.
Union
_clbyte As Byte 'Depending on the value of ctype, _cl* will contain the data.
_clubyte As UByte 'If ctype is tNull, the object will not contain any valid data.
_clshort As Short
_clushort As UShort
_clinteger As Integer
_cluinteger As UInteger
_cllong As Long
_clulong As ULong
_cllongint As LongInt
_clulongint As ULongInt
_clsingle As Single
_cldouble As Double
_clstring As ZString Ptr
End Union
Declare Sub _ClearCell ()
The _ctype variable is defined as cdatatype, the enumeration defined earlier in the code. This is the data type id and is used to reference the proper _cl* variable within the Union if the value of _cltype is not tNull. If _cltype's value is tNull then the Union is not accessed as the data object does not contain any valid value.
Since the data object can only contain a single type of data, the Union allows for efficient storage space while also providing the flexibility using any of the standard data types. Within a Union, all the variables share the same space in memory. The number of bytes allocated for the Union is the number of bytes of the largest variable, which in this case would be the Double which is 8 bytes or 64 bits. So, even though there are 13 variables defined in the Union, the total memory allocated for the data is only 8 bytes.
'Clears any data in object and sets to Null.
Sub dataobj._ClearData()
'Based on what the type is, clear the data.
Select Case _ctype
Case tNull
'Nothing to do here.
Case tByte
_clbyte = 0
Case tUByte
_clubyte = 0
Case tShort
_clshort = 0
Case tUShort
_clushort = 0
Case tInteger
_clinteger = 0
Case tUInteger
_cluinteger = 0
Case tLong
_cllong = 0
Case tUlong
_clulong = 0
Case tLongint
_cllongint = 0
Case tUlongint
_cllongint = 0
Case tSingle
_clsingle = 0
Case tDouble
_cldouble = 0
Case tString
'Deallocate string if present and set ptr to null.
If _clstring <> NULL Then
DeAllocate _clstring
_clstring = NULL
EndIf
End Select
'Set data type to Null.
_ctype = tNull
End Sub
The subroutine _ClearData clears any data within the data object and then sets the type id to tNull. The current id is examined and if it is not tNull, the proper variable is set to 0, in the case of non-string data, or if the object contains string data, the string is deallocated and the zstring pointer is set to NULL. The the object is already NULL, there is no need to reset the internal data.
The _ClearData is called in the object's Destructor and can also be called through the public interface via the ClearData subroutine.
Public Members
'Object constructor sets object to Null.
Constructor dataobj ()
_ctype = tNull
End Constructor
'Calls _ClearData to clear contents.
Destructor dataobj ()
_ClearData
End Destructor
The object Constructor is called when the object is created and the object Destructor is called when the object is destroyed either by going out of scope or when deleted. The Constructor sets the object to tNull since at this point since the object does not contain any valid data. The Destructor calls the private _ClearData subroutine to clear the data in the object.
There are several Let (assignment) operators defined for the data object, one for each variable type, and one for setting the data based on another data object.
'Sets object data using variable.
Operator dataobj.Let (rhs As Byte)
'Clear any existing data.
_ClearData
_ctype = tByte
_clbyte = rhs
End Operator
All of the variable-based assignment operators are codes the same, except for the passed parmeter type. Each assignment clears any data using the _ClearData subroutine, then sets the data type id and copies the actual data from the rhs variable into the appropriate _cl* varibale within the Union.
The Let operators are overloaded, which means the compiler will pick the appropriate Let based on the data type of the rhs parameter. There is one note of caution here: If a constant value is used in an assignment, the compiler will pick the closest data type to the value which usually will result in an Integer, Double or String assignment being selected. For example, if the constant value is 200, an Integer assignment will be selected even though the value could fit within a byte. The best practice is to use a standard variable in an assignment, or use one of the data suffixes when using a constant value so that the appropriate assignment operator is used.
Operator dataobj.Let (rhs As String)
Dim As Integer l = Len(rhs)
'Clear any existing data.
_ClearData
'Allocate space for string, or NULL for empty string.
_ctype = tString
If l > 0 Then
_clstring = Allocate(l + 1)
*_clstring = rhs
Else
_clstring = NULL
EndIf
End Operator
The string assignment operates in the same manner as the other assignment statements, except that a string is stored as a zstring, so the string space must be allocated to hold the string data. The amount of space allocated for the string is the length of the string + 1 byte for the terminating NULL character that a zstring requires.
Since a variable-length string cannot be contained within a Union, a zstring pointer is used as the internal string data type. This does add some slight overhead to the program, but not much more than using a variable-length string, since FreeBasic uses zstrings internally to store strings.
'Set the value of the data object using another data object.
Operator dataobj.Let (rhs As dataobj)
'Clears any current data and set object to NULL.
_ClearData
'Based on what the passed type is, set the data.
_ctype = rhs.DataType
Select Case _ctype
Case tNull
'Nothing to do here.
Case tByte
_clbyte = rhs
Case tUByte
_clubyte = rhs
Case tShort
_clshort = rhs
Case tUShort
_clushort = rhs
Case tInteger
_clinteger = rhs
Case tUInteger
_cluinteger = rhs
Case tLong
_cllong = rhs
Case tUlong
_clulong = rhs
Case tLongint
_cllongint = rhs
Case tUlongint
_clulongint = rhs
Case tSingle
_clsingle = rhs
Case tDouble
_cldouble = rhs
Case tString 'If a string is passed, allocate space for new string, else set to NULL.
Dim As String s = rhs
Dim As Integer l = Len(s)
If l > 0 Then
_clstring = Allocate(l + 1)
*_clstring = s
Else
_clstring = NULL
EndIf
End Select
End Operator
In addition to the standard variable assignments, the data object can be assigned the value of an existing data object. The code looks almost the same as the _ClearData code, except that the string is allocated and initialized rather than deallocated. If an empty string is passed to the object, the zstring pointer is set to NULL to signify an empty string.
While the Let operators act as the assignment operators, and the set of defined Cast operators return the object's data. Like the Let operators, the Cast operators are overloaded and the compiler selects the appropriate Cast based on the target variable type.
'Cast to data type. Allows returning data into a simple variable of type.
Operator dataobj.Cast () As Byte
If _ctype = tByte Then
Return _clbyte
Else
Return 0
EndIf
End Operator
The Cast operators first check to make sure that the object is of the appropriate data type, and then returns the current value. If the object is not of the appropriate type, either 0 is returned in the case of non-string data, or an empty string is returned in the case of string data. The data type of an object can be checked using the DataType property and should be checked if there is a question as to what type of data an object contains.
Operator dataobj.Cast () As String
If _ctype = tString Then
If _clstring <> NULL Then
Return *_clstring
Else
Return ""
EndIf
Else
Return ""
EndIf
End Operator
For string data, the zstring pointer is dereferenced, if not NULL, using the * operator and returned as a standard string if the object. If the zstring pointer is NULL or the object is not a string, then an empty string is returned.
Test Program
The following test program exercises the various members of the data object.
Filename: testdata.bas
/'****************************************************************************
*
* Name: testdata.bas
*
* Synopsis: Test program for dataobj.bi.
*
* Description: This program exercises the members of dataobj.bi file.
*
*
*
* Copyright 2009, Richard D. Clark
*
* The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies.
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************'/
#Include Once "dataobj.bi"
'Data Values.
Dim clbyte As Byte = -127
Dim clubyte As UByte = 127
Dim clshort As Short = -1000
Dim clushort As UShort = 1000
Dim clinteger As Integer = -200000
Dim cluinteger As UInteger = 200000
Dim cllong As Long = -300000000
Dim clulong As ULong = 3000000000
Dim cllongint As LongInt = -40000000000
Dim clulongint As ULongInt = 40000000000
Dim clsingle As Single = -100.2345
Dim cldouble As Double = 2345.6789
Dim clstring As String = "This is a string value"
Dim clbyte2 As Byte
Dim clubyte2 As UByte
Dim clshort2 As Short
Dim clushort2 As UShort
Dim clinteger2 As Integer
Dim cluinteger2 As UInteger
Dim cllong2 As Long
Dim clulong2 As ULong
Dim cllongint2 As LongInt
Dim clulongint2 As ULongInt
Dim clsingle2 As Single
Dim cldouble2 As Double
Dim clstring2 As String
'Unitialized Data object.
Dim myDO As dataobj
'Initlaized data object.
Dim myDO1 As dataobj
myDo1 = clstring
'Current value should be NULL.
If myDO.DataType = tNull Then
Print "Data object myDO is NULL."
Else
Print "myDO is type: " & myDO.DataType & "."
EndIf
'Current type should be string.
If myDO1.DataType = tString Then
Print "Data object myDO1 is a string."
Print "myDO1 data is: " & myDO1 & "."
Else
Print "myDO1 is type: " & myDO1.DataType & "."
EndIf
Print
'Print out values of each of the data type enum values.
Print "tNull = " & tNull
Print "tByte = " & tByte
Print "tUByte = " & tUByte
Print "tShort = " & tShort
Print "tUShort = " & tUShort
Print "tInteger = " & tInteger
Print "tUInteger = " & tUInteger
Print "tLong = " & tLong
Print "tUlong = " & tUlong
Print "tLongint = " & tLongint
Print "tUlongint = " & tUlongint
Print "tSingle = " & tSingle
Print "tDouble = " & tDouble
Print "tString = " & tString
Print
'Set myDO to each of the data types.
myDO = clbyte
Print "myDO is type: " & myDO.DataType
clbyte2 = myDO
Print "myDO data is: " & clbyte2
Print
myDO = clubyte
Print "myDO is type: " & myDO.DataType
clubyte2 = myDO
Print "myDO data is: " & clubyte2
Print
myDO = clshort
Print "myDO is type: " & myDO.DataType
clshort2 = myDO
Print "myDO data is: " & clshort2
Print
myDO = clushort
Print "myDO is type: " & myDO.DataType
clushort2 = myDO
Print "myDO data is: " & clushort2
Print
myDO = clinteger
Print "myDO is type: " & myDO.DataType
clinteger2 = myDO
Print "myDO data is: " & clinteger2
Print
myDO = cluinteger
Print "myDO is type: " & myDO.DataType
cluinteger2 = myDO
Print "myDO data is: " & cluinteger2
Print
myDO = cllong
Print "myDO is type: " & myDO.DataType
cllong2 = myDO
Print "myDO data is: " & cllong2
Print
myDO = clulong
Print "myDO is type: " & myDO.DataType
clulong2 = myDO
Print "myDO data is: " & clulong2
Print
myDO = cllongint
Print "myDO is type: " & myDO.DataType
cllongint2 = myDO
Print "myDO data is: " & cllongint2
Print
myDO = clulongint
Print "myDO is type: " & myDO.DataType
clulongint2 = myDO
Print "myDO data is: " & clulongint2
Print
myDO = clsingle
Print "myDO is type: " & myDO.DataType
clsingle2 = myDO
Print "myDO data is: " & clsingle2
Print
myDO = cldouble
Print "myDO is type: " & myDO.DataType
cldouble2 = myDO
Print "myDO data is: " & cldouble2
Print
'Test the object assignment.
myDO = myDO1
Print "myDO is type: " & myDO.DataType
clstring2 = myDO
Print "myDO data is: " & clstring2
Print
'Clear the data in both objects.
myDO.ClearData
Print "myDO is type: " & myDO.DataType
myDO1.ClearData
Print "myDO1 is type: " & myDO1.DataType
Sleep
The output of the program:
Data object myDO is NULL.
Data object myDO1 is a string.
myDO1 data is: This is a string value.
tNull = 0
tByte = 1
tUByte = 2
tShort = 3
tUShort = 4
tInteger = 5
tUInteger = 6
tLong = 7
tUlong = 8
tLongint = 9
tUlongint = 10
tSingle = 11
tDouble = 12
tString = 13
myDO is type: 1
myDO data is: -127
myDO is type: 2
myDO data is: 127
myDO is type: 3
myDO data is: -1000
myDO is type: 4
myDO data is: 1000
myDO is type: 5
myDO data is: -200000
myDO is type: 6
myDO data is: 200000
myDO is type: 7
myDO data is: -300000000
myDO is type: 8
myDO data is: 3000000000
myDO is type: 9
myDO data is: -40000000000
myDO is type: 10
myDO data is: 40000000000
myDO is type: 11
myDO data is: -100.2345
myDO is type: 12
myDO data is: 2345.6789
myDO is type: 13
myDO data is: This is a string value
myDO is type: 0
myDO1 is type: 0