A Delphi programozási nyelv

Alprogramok, modulok

Szintaxis

Modulokra bontás

A Delphi nem követeli meg a modulokra bontást, ennek ellenére gazdag eszközkészletet ad a modularizációhoz, ami a grafikus, eseményvezérelt programok készítéséhez elengedhetetlen. Alapértelmezésben modulokra bontott programot készít a Delphi. Kétféle modul van: főmodul (lehet program, library, package) és egység (unit).

Unitok kinézete

 Delphiben minden egységnek van neve (projectenként egyedi kell legyen), amely megegyezik az egységet tartalmazó forrásfájl nevével. Az egység támogatja a programozó-fordító szerződés modellt, mivel a fordító ellenőrzi a kód helyes felhasználását. Egy unit lefordítható tárgykód formátumra, amely a unit forráskódja nélkül is hozzáfűzhető más programok forráskódjához, így védi a forráskódot. Használata esetén csak a hivatkozott kódrészletek kerülnek bele a lefordított kódba, valamint az esetleges initialization finalization szekciók kódjai.

Az egység általános szerkezete, az összes lehetséges részt beleértve, a következő:

unit unit_neve; interface //ami kifele látszik (export rész) uses //ezekre a külső unitokra hivatkozunk (már az interface részben)  unit_név1, unit_név2; type // exportált típusok  TÚjTípus1 = TípusDefiníció; const // exportált konstansok  Konstans1 = KonstansDefiníció; var // globális változók  Változó1: VáltozoDefiníció; //itt következnek az exportált függvények, eljárások procedure Proc1; implementation //megvalósítás uses // ezekre a külső unitokra hivatkozunk // (csak azok kellenek ide, melyeket az interface-nél nem soroltunk fel) unit_név3, unit_név4, unit_név5; var // kívülről nem látható (rejtett) globális változók Változó2: VáltozoDefiníció; //itt kell megvalósítani minden exportált eljárást procedure Proc1; begin // a Proc1 eljárás megvalósítása end; initialization // kezdeti értékek beállítása finalization // itt szabadíthatjuk fel az inicializáló részben lefoglalt erőforrásokat //(csak a Delphi 32 bites verzióiban) end.

 Természetesen egy unitnak nem kell feltétlenül fenti elemek mindegyikét tartalmaznia. A minimális egység (az úgynevezett üres egység) a következő (új egység hozzáadásakor ilyen kódot generál a Delphi):

unit Unit1; interface implementation end.

Minden egység implicit hivatkozik a System egységre. Az egységek nem hivatkozhatják körbe egymást az interface részben, viszont úgy már lehetséges a hivatkozás, ha a kör legalább egy tagja csak az implementation részben hivatkozik a következőre.

A const, type, var szekciók sorrendje tetszőleges lehet, viszont fontos a definiálási sorrend: csak már definiált névre lehet hivatkozni. Példa előzetes definiálásra:

type RA=class; RB=class X:RA; end; RA=class ZZ:RB; end;

A hivatkozott egységek minden exportált szimbóluma elérhető a hivatkozó egységben. Ha több egység exportál ugyanazzal a névvel szimbólumot, akkor minősített névvel különböztethetjük meg őket, pl. Unit1.MyType. Az újabb Delphi verziók lehetővé teszik az egységek „névtérbe” szervezését, ha az egység nevét pontokkal tagoljuk (pl. unit MyCompany.MyWidgets.MyUnit;). A névterek neve nem szimbólum, hanem az egység nevének része. Bizonyos esetekben az egységre való hivatkozásnál elhagyható a névtérrész.

Főmodulok típusai

Program: egy végrehajtható programhoz tartozó főmodul. Kiterjesztése általában .dpr. Egy program tartalmaz programfejlécet (program név), a hivatkozott egységek listáját (uses), tetszőleges számú deklarációt, végül a főprogramot begin ... end. között. A főprogramnak nincsenek paraméterei és visszatérési értéke. A parancssori paramétereket a ParamCount és ParamStr könyvtári függvényekkel érhetjük el, a program visszatérési értékét pedig a globális ExitCode változóban adhatjuk meg. A klasszikus „Hello, World!” alkalmazás objektumorientált változata a következőképpen néz ki:

program HelloWorld; {$APPTYPE CONSOLE} type THelloWorld = class procedure Put; end; var HelloWorld: THelloWorld; procedure THelloWorld.Put; begin WriteLn('Hello, World!'); end; begin HelloWorld := THelloWorld.Create; try HelloWorld.Put; finally HelloWorld.Free; end; end.

Library: különálló fájlba fordítandó függvénykönyvtár (DLL). A DLL által exportált alprogramokat akár más nyelven írt programból is meghívhatjuk. A DLL modul tartalmaz egy fejlécet (library név), tetszés szerint uses részt, tetszőleges számú deklarációt, a végén pedig exports részt és tetszés szerint főprogramot, amely a könyvtár betöltésekor kerül végrehajtásra. Az exports részben felsoroljuk a könyvár által exportált alprogramokat. Mindegyikhez adhatunk egy álnevet (pl. exports DoSomethingABC name 'DoSomething';), ha ezt nem tesszük meg, akkor az eredeti nevével kerül exportálásra.

Package: a DLL-lel szemben ez egy kizárólag Delphiben használható könyvtár. Egy csomag Delphi egységeket tartalmaz, valamint megadhatók neki függőségek. Készíthetünk „tervezési idejű” (design-time) csomagot, amellyel a Delphi IDE képességeit bővíthetjük ki vagy a programokban felhasználható komponenseket valósíthatunk meg. A csomag főmoduljának kiterjesztése .dpk. Ha lefordítjuk, egy .bpl kiterjesztésű fájlt kapunk, amelyet a Delphi „Component -> Install Packages...” menüpontjával tölthetünk be az IDE-be. A programok által hivatkozott csomagokat a fordító statikusan köti a programhoz, de lehetőség van a dinamikus betöltésre is. (A programhoz csatolandó csomagokat a projekt tulajdonságaiban állíthatjuk be.)

A csomag főmodulja az alábbi elemeket tartalmazza:

package CsomagNév; requires MásikCsomag1, MásikCsomag2; contains Unit1, Unit2; end.

A csomagok nem hivatkozhatják körbe egymást a requires részben. Egy csomag nem tartalmazhatja a függőségei által tartalmazott egységeket, és egy programhoz nem csatolható két olyan csomag, amely tartalmaz azonos egységet.

Alprogram, programszerkezet

A Pascalban alprogram deklarálására kétféle lehetőség kívánkozik: az eljárás (procedure) és a függvény (function). Az egyetlen különbség köztük az, hogy a függvénynek van visszatérési értéke, míg az eljárásnak nincs. Pascal terminológiában az eljárást és a függvényt összefoglaló néven rutinnak nevezik. Lehetőség van az alprogramok egymásba ágyazására.

Eljárás

 Az eljárások meghatározásának szintaxisa:

procedure EljárásNév(paraméterek); eljárás direktívák; begin eljárás törzse; end;
Függvény
function EljárásNév(paraméterek): VisszatérésiÉrtékTípus; eljárás direktívák; begin függvény törzse; end;

A visszatérő értéket a

 Result:=kifejezés;

vagy

 Függvénynév:=kifejezés;

alakban adhatjuk meg. Ezeket az értékadásokat több helyen is kiadhatjuk a függvénytörzsben. {$X+} állapotban a Result nevet felhasználhatjuk kifejezésekben a benne tárolt érték kiolvasására, viszont a függvénynév kifejezésben való felhasználása rekurzív függvényhívást idéz elő.

 A Turbo Pascaltól eltérően a Delphiben a függvények visszatérési értéke nem csak a megszokott egyszerű típus lehet, hanem összetett típusú értéket is visszaadhat (pl. rekord vagy tömb típus). Nem lehet azonban file vagy object típusú értéket visszaadni (class típusút viszont igen).

 Az eljáráshívás a következőképpen történik:

EljárásNév(átadandó paraméterek);

Természetesen az átadott paraméterek száma és típusa meg kell, hogy egyezzen az eljárásfejben deklarálttal. {$X+} állapotban függvény hívása is lehet önálló utasítás (ez eléggé elterjedt pl. Windows API függvények hívásakor, ezért az X direktíva alapértelmezésben be van kapcsolva). A függvények szerepelhetnek kifejezésben is, az eljárások nem.

Alprogramok előzetes deklarációja

Object Pascal nyelvben minden azonosítót felhasználása előtt deklarálni vagy definiálni kell. Ha két, egymást kölcsönösen hívó függvényt vagy eljárást szeretnénk készíteni, akkor az első alprogram egy később deklarált alprogramot hívna. Ezt a problémát megoldhatjuk úgy, hogy a második alprogramot előzetesen deklaráljuk.

Az előzetes deklaráció az alprogramnak csak a fejlécét tartalmazza, amelyet a forward direktíva és egy pontosvessző követ. Ezután az előzetesen deklarált eljárást vagy függvényt ugyanúgy hívhatjuk, mint a már definiáltakat. Az előzetesen deklarált alprogram teljes definíciója ezután bárhol megadható. Ekkor már elég a procedure vagy function kulcsszó után csak a függvény vagy eljárás nevét megadni. Pl:

function max(a,b: integer) : integer; forward; function max(a,b: integer) : integer; begin if a > b then max := a else max := b; end;

De lehet a következőképpen is:

function max(a,b: integer) : integer; forward; function max; begin if a > b then max := a else max := b; end;

Nincs szükség a forward kulcsszóra, ha a deklaráció egy egység interface részében, rekord vagy osztály definíciójában van, mert ezek mindig előzetes deklarációk.

Paraméterátadás

A paraméterlistában a formális paramétereket a szokásos név: típus formában adhatjuk meg, pontosvesszővel elválasztva. Lehetőség van egy típus előtt több nevet felsorolni, valamint a névcsoportot megelőzheti a paraméter módját jelző direktíva. Az utolsó néhány paraméternek alapértelmezett értéket is adhatunk.

Paraméterek átadására és módjára vonatkoznak az alábbi direktívák:

 Paraméterek cím szerinti átadásának csak régi stílusú stringek, nagyobb rekordok vagy tömbök esetén van értelme. A Delphi objektumok mindig változatlanul adódnak át, mivel maguk is hivatkozások. Ebből kifolyólag egy objektum referenciakénti átadásának nem igazán van értelme, hiszen ez "hivatkozás a hivatkozásra" átadásnak felel meg. (Persze néha elképzelhető, hogy épp erre van szükségünk.)

 A paraméterátadás szempontjából érdekes még a Delphi hosszú stringjeinek esete, ezek ugyanis (bár maguk is hivatkozások) eltérően viselkednek az előbb leírtaktól. Az érték szerint átadott string csak memória felhasználás szempontjából viselkedik hivatkozásként (csak egy mutató kerül a verembe). Ha megváltoztatjuk a string értékét, az eredeti string változatlan marad. Referenciaként átadva az eredeti értéket változtathatjuk.

Az alprogramoknak érték és cím szerint átadott változók típusa tetszőleges lehet, kivéve a fájltípusokat (text, file of <típus>, file), amelyeket csak cím szerint adhatunk át.

Érdemes kiemelni két további lehetőséget, melyek segítségével eljárás- és függvénysablonokat tudunk definiálni.

Az alprogramok paraméterlistáján függvény és eljárás is szerepelhet. Ilyenkor létre kell hozni a megfelelő eljárás- és függvénytípusokat, pl.:

type fgv = function(x,y: integer) : integer;

Az Object Pascal lehetővé teszi, hogy a formális paraméterlistán elhagyjuk a paraméterek típusát. Ezt a módszert akkor használhatjuk, ha az alprogram működése a típustól független.

Ha a típus nincs megadva, akkor egy típus nélküli pointer vesz át, de ez nem ugyanaz, mintha Pointer típusú paramétert várnánk! Erre bármilyen változó illeszkedik, míg a Pointer-re csak Pointer típusú. Ilyenkor kötelező minősíteni (var, out, const), mert érték szerint nem lehet átvenni címet. Az ilyen paramétereket csak úgy tudjuk felhasználni, ha explicit típuskényszerítést használunk.

procedure Dummy(var Variable); begin ... end;

Extra paraméterek Delphiben

 Delphiben van mód néhány különleges paraméter használatára is, melyekkel az alprogramhívások nyújtotta lehetőségek bővülnek ki.

Nyitott tömb paraméter

  A formális paraméterlistában nyitott tömb használatával elérhetjük, hogy az alprogram tetszőleges méretű aktuális paramétertömbbel hívható legyen. A nyitott tömböt a következő módon deklaráljuk: array of Típus, ahol a Típus a tömb elemeinek típusát jelenti. (Ez nem összetévesztendő a dinamikus tömb típussal, amely változó deklarációjában fordul elő.) A tömb indexhatárait a Low() és High() függvény használatával kérdezhetjük le.

 Nyitott tömb paraméter használata esetén az alábbiakra kell ügyelni:

  1. ezeket a tömböket csak elemenként lehet elérni, tehát két nyitott tömb esetén nincs lehetőség arra, hogy a t1 := t2 értékadó utasítást használjuk.
  2. aktuális paraméter helyén csak akkor állhat nyitott tömb, ha a formális paraméter is  nyitott vagy típus nélküli tömb.
  3. használható érték szerinti, változó vagy konstans paraméterátadás esetén egyaránt.
  4. nyitott tömb paramétert használó alprogram meghívható úgy is, ha az aktuális paraméter helyén a tömb konkrét elemeit soroljuk csak fel. (pl.:  osszeg ( [ 8, 10, 12 ] ) )
  5. ha a nyitott tömb char típusú elemeket tartalmaz, akkor az aktuális paraméter helyén string konstans is állhat, azonban string típusú változó nem használható.

  A nyitott tömb paraméter használatát mutatja a következő példa :

function Összeg(tomb: array of Integer): Integer; var i, ossz: Integer; begin ossz:=0; for i:=0 to High(tomb) do ossz:=ossz+tomb[i]; Összeg:=ossz; end;

Típus nélküli nyitott tömb paraméter

 Lehetőség van arra is, hogy olyan nyitott tömböt deklaráljunk, amelynek az elemei különböző típusúak lehetnek. Ezt az array of const vagy az ezzel ekvivalens array of TVarRec deklaráció megadásával használhatjuk. Az alprogramban az adott tömbelem típusát egy egyszerű case szerkezettel kérdezhetjük le. Tipikus példa ennek az eszköznek a használatára a SysUtils egységben található Format függvény, amely a C-beli printf-hez hasonlóan egy formátumstringbe helyettesíti be a paramétereit.

Példa a különféle típusok kezelésére (a SysUtils stringgé alakító függvényeit használja):

function MakeStr(const Args: array of const): string; var I: Integer; begin Result := ; for I := 0 to High(Args) do with Args[I] do case VType of vtInteger: Result := Result + IntToStr(VInteger); vtBoolean: Result := Result + BoolToStr(VBoolean); vtChar: Result := Result + VChar; vtExtended: Result := Result + FloatToStr(VExtended^); vtString: Result := Result + VString^; vtPChar: Result := Result + VPChar; vtObject: Result := Result + VObject.ClassName; vtClass: Result := Result + VClass.ClassName; vtAnsiString: Result := Result + string(VAnsiString); vtUnicodeString: Result := Result + string(VUnicodeString); vtCurrency: Result := Result + CurrToStr(VCurrency^); vtVariant: Result := Result + string(VVariant^); vtInt64: Result := Result + IntToStr(VInt64^); end; end;

Nyitott string paraméter

  A Delphiben használhatunk tetszőleges hosszúságú string paramétert is a formális paraméterlistában, ehhez a paraméter típusát string helyett OpenString típusúnak kell deklarálni:

procedure szöveg ( var sz : OpenString );

A formális paraméter hossza itt is mindig megegyezik az aktuális paraméter hosszával, ami bármilyen string típusú változó lehet. Arra azonban ügyeljünk, hogy ez a paraméterátadás nem használható érték szerinti és konstans paraméterátadásnál, ilyenkor a Delphi az ilyen típussal deklarált paramétereket hagyományos string típusúnak tekinti.

Túlterhelés

 (Csak Delphi 4-től)

Azonos névvel, de különböző paraméterlistával és működéssel több alprogramot is deklarálhatunk az overload direktíva segítségével. A fordítóprogram az aktuális paraméterlista alapján dönti el, hogy melyiket hívja. Pl:

function oszt(x,y: Real): Real; overload; begin Result := x/y; end; function oszt(x,y: Integer): Integer; overload; begin Result := x/y; end; // Híváskor: writeln(oszt(3,2)); //1 writeln(oszt(3.0,2.0)); //1.5

Ha a paraméterlista nem egyértelmű, fordítási hibát ad. Pl:

procedure Z(X:Byte); overload; procedure Z(X:Byte; Y:Byte=0); overload; … Z(3); // fordítási hiba!

Túlterhelt alprogramok ugyanolyan „típusúak” kell, hogy legyenek, azaz függvényt és eljárást nem lehet keverni.

Osztályon belüli túlterhelés: Származtatott osztályban egy metódus overload direktívával való deklarálása egy új metódust fog létrehozni az öröklött metódus elrejtése nélkül, ha a paraméterlistája különbözik az ősétől. Virtuális metódus túlterhelése a reintroduce direktívával lehetséges:

procedure Test(I: Integer); overload; virtual; procedure Test(S: string); reintroduce; overload;

Egy osztályon belül nincs lehetőség azonos néven több túlterhelt metódus exportálására a published szekcióban (máshol megengedett).

Rekurzió

A Delphiben a szokásos módon írható le a rekurzió:

function fac(N:integer):integer; begin if N <= 0 then Result := 1 else Result := N * fac(N-1); end;

Direktívák

Bizonyos direktívákkal befolyásolhatjuk egy alprogramból generált kódot.

inline

Az inline kulcsszóval azt jelezzük, hogy a fordító az alprogram hívásának helyére bemásolhatja az alprogram kódját. Ez nem jelent kötelezettséget, a fordító dönthet úgy, hogy egy adott helyen mégis alprogramhívást generál.

function Max(const X,Y,Z: Integer): Integer; inline; begin if X > Y then if X > Z then Result := X else Result := Z else if Y > Z then Result := Y else Result := Z end;

external

Ha egy alprogram különálló tárgykód fájlban (.OBJ) vagy DLL-ben található, akkor external deklarációt kell felvennünk hozzá. .OBJ fájl esetén csak az external; kulcsszót adjuk meg. DLL-nél meg kell adni a DLL fájl nevét és esetleg az exportált nevét string konstans formájában:

function SomeFunction(S: string): string; external 'strlib.dll'; function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer; stdcall; external 'user32.dll' name 'MessageBoxA';

register, pascal, cdecl, stdcall, safecall

Ha más nyelven írt DLL-ből szeretnénk meghívni egy alprogramot, szükség lehet a hívási konvenció megadására. A hívás csak akkor fog jól működni, ha a hívó és a hívott helyén azonos konvenciót adunk meg.

Általában a register-t célszerű használni, mivel ez a leggyorsabb, így ez az alapértelmezett is. A cdecl-re C/C++ függvény hívásakor lehet szükség, a stdcall-t pedig általános használatra javasolják külső kód meghívására. A pascal konvenció csak kompatibilitási okok miatt maradt meg.