CA - Visual Objects

Típusok, típuskonstrukciók

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ípusLeírás
usualváltoztatható típus explicit deklarálása
intplatformfüggő előjeles egész
floatplatformfüggő lebegőpontos
shortint16 bites előjeles egész
longint32 bites előjeles egész
byte8 bites előjel nélküli egész
word16 bites előjel nélküli egész
dword32 bites előjel nélküli egész
real4egyszeres lebegőpontos
real8kétszeres lebegőpontos
logiclogikai típus
nilcsak egy értéket vehet fel, a nil -t
voidolyan függvényeknél használjuk, amiknek nincs visszatérő értéke
datedátum
stringkaraktertömb
symbolfordítási idejü string, nem macskakörmök között kell megadni, hanem egy # -el az elején
codeblockolyasmi 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ő:

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átorLeí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