Típusszerkezet
Az előző fejezetben láthattuk, hogy nem feltétlenül kell megadni a változó típusát, de ha akarjuk az as kulcsszóval megtehetjük. Amennyiben nem adunk meg típust,
a változó akármilyen típusú értéket felvehet. Ez ugyanaz mintha explicit módon usual típusúnak deklarálnánk.
Ha megadtuk a típust deklaráláskor, akkor már nem vehet fel akármilyen értéket. Ha egy változót statikusnak deklarálunk, akkor az a program befejezéséig élni fog.
A static kulcsszó viszont más kontextusban mást jelenthet, például struktúráknál és konstansoknál a láthatóságot az adott modulra korlátozza.
Deklaráció az első (nem deklarációs) utasítás előtt kell álljon (vagyis a függvény elején).
static local i // as usual
local j := 5 as int
i := 2
? i
i := "alma"
// j := "dio" // fordítási hiba
? i, j
// output:
2
alma 5
Elemi típusok
A fontosabb beépített típusokat tartalmazza az alábbi táblázat, de van még jópár.
Típus | Leírás |
usual | változtatható típus explicit deklarálása |
int | platformfüggő előjeles egész |
float | platformfüggő lebegőpontos |
shortint | 16 bites előjeles egész |
longint | 32 bites előjeles egész |
byte | 8 bites előjel nélküli egész |
word | 16 bites előjel nélküli egész |
dword | 32 bites előjel nélküli egész |
real4 | egyszeres lebegőpontos |
real8 | kétszeres lebegőpontos |
logic | logikai típus |
nil | csak egy értéket vehet fel, a nil -t |
void | olyan függvényeknél használjuk, amiknek nincs visszatérő értéke |
date | dátum |
string | karaktertömb |
symbol | fordítási idejü string, nem macskakörmök között kell megadni, hanem egy # -el az elején |
codeblock | olyasmi mint a függvény pointer vagy egy makró, de például lehet tömbnek egy eleme |
A
nil adattípusként és konstansként is megjelenik. Ha egy változónak
nil értéket adunk, akkor elveszti az addigi típusát. A
usual típusúnak
deklarált változók kezdőértéke
nil.
A logikai típus
true vagy
false értéket vehet fel, ezeket megadhatjuk
.T. illetve
.F. alakban is.
Ha egy változót
usual -ként deklaráltunk, attól még használhatjuk explicit típusú paramétert váró függvények hívására. A
void
kivételével bármilyen típusú értéket felvehet. Struktúrákat és dimenzionált tömböket nem jellemezhetünk
usual -ként.
Típuskonstrukciók
Az alábbi típuskonstrukciókat használhatjuk:
Tömb típus(ok)
CA-VO -ban kétféle tömb típus létezik, a dinamikus és a dimenzionált. Mint ahogy a változóknál is, úgy a tömböknél sem kötelező típust megadni, egy tömbben akár
különböző típusú elemek is lehetnek. A tömbök címzése 1-től indul.
A dinamikus tömbök mérete megváltoztatható futási időben. Az elemek maximális száma dimenziónként 65535, de a dimenziók számára nincs korlát. Az egyes elemekre a
[] operátorral hivatkozhatunk, ez legfeljebb három indexet tartalmazhat. Ha több dimenziót szeretnénk, akkor az ArrayGet() illetve ArrayPut()
függvényeket kell használnunk.
local t1[3]
local t2[2, 2] // vagy t2[2][2]
t1 := { 2, "alma", nil }
t2 := { { "alma", "dio" }, { 2, "mogyoro" } }
? t1[1], t1[2], t1[3]
? t2[1][1], t2[1, 2] // mindkét fajta cimzési mód érvényes
// output:
// 2 alma nil
// alma dio
Tömböket feltölthetünk egy bizonyos értékkel az
AFill() függvény segítségével.
Érdekesség, hogy egy dinamikus tömb hivatkozhat egy másik tömbre, így az egyiken végrehajtott módosítás a másikra is kihat. Két tömb akkor
egyenlő, ha egymásnak hivatkozásai.
local t1 := { 1, 2, 3 }, t2 // as array
local i as int
t2 := ASize(t1, 5) // t1 és t2 is 5 elemű tömbök lettek
t2[4] := "alma"
for i := 1 to 5
? t1[i]
next
t1[5] := "namostmitortenik"
? t2[5]
// output: 1 2 3 "alma" NIL namostmitortenik
A
dimenzionált tömbök mérete már fordítási időben is rögzített. Ezeknek a típusát megadhatjuk explicit módon is, ekkor csak olyan típusú elemeket tudunk beléjük pakolni.
Inicializálni nem lehet őket, sőt a fentebb látott
{} sem működik velük.
A dimenziók száma itt már csak három lehet, ha többet akarunk, akkor dinamikus tömböt kell használnunk. A dimenzionált tömb mérete futási időben már nem módosítható.
Nem használhatóak argumentumként és visszatérési értékként. Gyorsabbak mint a dinamikus tömbök.
local dim t1[4] as int
t1[1] := 1
t1[2] := 2
t1[3] := 3
t1[4] := 4
// t1[5] := 5 // fordítási hiba
// 3[t1] := 3 // ez C-ben müködik, de itt nem :)
// t1[3] := "alma" // fordítási hiba
? t1[1], t1[2], t1[3], t1[4]
Direkt szorzat típus
Struktúrákat a structure kulcsszóval definiálhatunk, az adattagjait a member kulcsszóval adhatjuk meg. Az eddigiekkel ellentétben amikor
egy struktúra egy példányát deklaráljuk, az as kulcsszó helyett lehetőség van az is használatára is. A kettő között az a lényeges különbség, hogy az is
a stacken foglalja le a memóriát az objektumnak, majd automatikusan felszabadítja az élettartam végén. Az as esetében viszont nekünk kell ezeket elvégezni a MemAlloc() és a
MemFree() függvényekkel (és akkor a heap-en fog létrejönni). Az objektum adattagjaira a . operátorral hivatkozhatunk.
structure Citrom
member Meret as int
member Szin as int
member dim Tomb[5] as int
function Start(p)
local c is Citrom
c.Meret := 10
c.Szin := 2
_Accept("")
return nil
Ha egy struktúrát statikusnak deklarálunk, akkor csak abban a modulban lesz látható, amiben deklaráltuk.
Amennyiben a struktúra egyik tagja egy másik struktúra, és azt
as kulcsszóval deklaráltuk, akkor annak is nekünk kell foglalni memóriát.
Amik
nem lehetnek tagok: dinamikus tömbök,
usual,
string vagy
float változók,
vagy bármi amihez szemétgyűjtés kell (tipikusan ilyen mulatságos errort fogunk kapni, hogy
XYZ needs a KID). A
, operátorral egy darab
member
kulcsszó után is felsorolhatjuk a tagokat.
structure Citrom
member Meret as int, Szin as int // a típust mindkét esetben meg kell adni!
structure Banan
member Meret as int
member Barat as Citrom
function Start(p)
local b as Banan
b := MemAlloc(_sizeof(Banan))
b.Barat := MemAlloc(_sizeof(Citrom))
// blabla
MemFree(b.Barat)
MemFree(b)
//? b.Meret // futási idejü hiba lehet; jobb esetben csak memóriaszemét
_Accept("")
return nil
Unió típus
Erről sajnos a reference guide sem írt, csak az elektronikus help-ben találtam róla valamit, de fordítási hibákat dobott rá. A lényege az, hogy minden member
a 0 offsettől indul, tehát egy négy bájtos memóriaterületre hivatkozhatnék úgy is mint két darab két bájtosra. A példaprogram nem fordul, de azért bemásolom.
* nem fordul
union wb
member w as word
byte blo as byte
byte bhi as byte
function x
local u is wb
u.w := 0x1234
? u.blo // 52 (=0x34)
? u.bhi // 18 (=0x12)
Felsorolási típus
Erről már az beépített help sem írt, úgy néz ki nem is támogatott. A Vulcan.NET -ben elvileg már van (nem próbáltam).
* nem fordul
enum Szin
member Kek
member Piros
function Start(p)
local s as Szin
s := Szin.Kek
_Accept("")
return nil
Típuskonverziók
Más nyelvekhez hasonlóan itt is van implicit és explicit típuskonverzió. Ha egy kifejezésben különböző típusú változók
szerepelnek, akkor a nagyobb tárolókapacitást igénylő típusra fog a többi konvertálódni.
Az explicit típuskonverziót <tipus>(<változónév>) formában válthatjuk ki, amennyiben létezik konverzió az adott típusok között. A többire vannak függvények,
például az Str() függvény egy számot stringgé konvertál, vagy a Val() ami stringet számmá.
local i := 4.0 as int // implicit; a fordító figyelmeztet
local f as float
f := float(i) // explicit
? Str(f) + "alma"
// output: 4alma
Amennyiben egy változó típusát mi akarjuk megváltoztatni, akkor a
_CAST szócskát is meg kell adni paraméternek. Ekkor viszont az érték nem változik meg, sőt
veszélyes is lehet, például ha egy
int -et castolunk
float -ra, majd ki akarjuk irni, az jó eséllyel futási idejü hiba lesz.
* ez elég buta példa, de például igy lehetne egy valos számot shiftelgetni
local i as longint
local f := 2.5 as real4
i := long(_cast, f)
i := i >> 1
f := real4(_cast, i)
? f
Változók, konstansok
Konstans változókat nem lehet úgy definiálni mint pl. C-ben, viszont az ottani #define direktívához hasonló dolog van itt is, a define. Az ilyen értékeknek nincs típusa
és kötelező kezdőértéket adni nekik. A főprogramon kívül kell deklarálni őket, külön elemnek számítanak a modulban. Létrehozás után már nem kaphatnak értéket. A kezdőérték lehet operátorokat, literálokat vagy más konstansokat
tartalmazó kifejezés is. Lehetnek statikusak vagyis csak abban a modulban látszódnak majd ahol deklaráltuk őket. Meglepő módon azt, hogy egy konstans definiálva van-e az #ifdef direktívával
kérdezhetjük le (a reference guide szerint ez is utasítás). Nem irtam el, télleg az egyiknél van hashmark a másiknál nincs...biztos van benne logika.
define a := 2
static define b := (a + 5) * 6
function Start(p)
//a := 3 error: assign to non-lvalue
? a, b
_Accept("")
return nil
Kifejezések, operátorok
Kifejezés kiértékelésekor itt is a precedenciák döntenek, ha nem zárójelezzük máshogy. Az azonos precedenciájú műveleteket balról jobbra értékeli ki.
A default sorrend a következő:
- minden más, pl. függvényhívás
- előjel (+ -)
- hatványozás (^ **)
- szorzás, osztás, maradékképzés (* / %)
- összeadás, kivonás, konkatenáció (+ -)
- összehasonlítás (< > <= >= = == <> # != $)
- logikai nem (.NOT. !)
- logikai és (.AND.)
- logikai vagy (.OR.)
- értékadások (:= *= /= %= += -=)
A következő táblázat olyan operátorok leírását tartalmazza amiket egy mezei programozó nem találna ki elsőre.
Operátor | Leírás |
++ | növelés 1-el, ugyanaz mint C-ben |
-- | csökkentés 1-el, ugyanaz mint C-ben |
^ | hatványozás |
** | hatványozás |
<> | nem egyenlő |
# | nem egyenlő |
$ | részstringje-e |
= | egyenlő, bármely adattípusra |
== | egyenlő, stringeknél beleértve a záró szóközöket is |
_And() | bitenkénti és |
_Or() | bitenkénti vagy |
_Chr() | numerikusból string |
-> | alias azonosító |
& | makró fordítása és végrehajtása |
@ | referencia |
Makrók segítségével mindenfélét lehet hackelni, ezeket stringként kell deklarálni, majd az
& operátorral fordíthatjuk és futtathatjuk őket.
Ez egy futási idejű fordítóprogramot hív meg, de csak kifejezéseket tud lefordítani.
local mymacro := "DToC(Today())"
local s1 := "2"
local s2 := "+ 3"
? &mymacro // a mai dátumot fogja kiirni
? "&mymacro." // nálam nem müködik, elvileg kiirná, hogy DToC(Today())
? &(s1 + s2) // 5