next up previous contents
Elõre: Módosító jelzõk Fel: Tárolási osztályokhatáskörök Vissza: Egyszerû adatdeklarátorok

Függvények definíciója és deklarációja

Az eddigiekben tárterület foglaló tárolási egységek definiálásáról és deklarációjáról volt szó, most tekintsük át a kódgeneráló program egységeket ebbõl a szempontból. A függvénydefiníció általános formája:

tárolási osztály  típus  azonosító( formális paraméterlista)  blokk

A tárolási osztály specifikátor vagy üres (globális függvény), vagy static (modulra lokális függvény). A típus a függvény által visszaadott érték típusa, ez gyakorlatilag tetszõleges lehet (kivétel, hogy függvény nem adhat vissza tömböt vagy függvényt, de visszaadhat ezeket megcímzõ mutatót). A függvényazonosítóra ( azonosító) az azonosítókról általában elmondottak érvényesek. A ( )-ek között álló formális paraméterlista a paraméterek deklarációjára szolgál, ahány deklaráció szerepel benne, annyi paramétere van a függvénynek (akár egy sem, mint például a main() a pelda.c-ben). A blokk - { }-ek között - deklarációkat, blokkra lokális adatdefiníciókat és végrehajtható utasításokat tartalmazhat. Például a:

           static double deg_to_rad(double x)
           {  
              ...
           }
definiálja a deg_to_rad azonosítójú modulra lokális függvényt, amely egy dupla pontosságú lebegõpontos paraméterrel rendelkezik és ugyanilyen típusú értékkel tér vissza (a blokkban használható utasításokat a következõ részekben fogjuk ismertetni). Vagy tekintsünk egy másik példát:
           int read(int handle, char *buf, unsigned len)
           {  
              ...
           }
Ez a definíció létrehozza a read azonosítójú globális függvényt, amely (elõjeles) egész értéket ad vissza és három paramétert vár, ezek típusa sorrendben egész, karaktert megcímzõ pointer és elõjel nélküli egész.

Ha a formális paraméterlistában ... áll az utolsó paraméter helyett, akkor ezzel azt jelezzük, hogy az adott függvénynek a deklaráltakon kívül további, ismeretlen számú és/vagy ismeretlen típusú paramétere lehet. Erre jellemzõ példa az stdio.h include file-ban deklarált

            int printf(char*, ...)
standard függvény (mely az elsõ paraméterében adott sztringben lévõ specifikáció szerint a szabványos kimeneti állományra - tipikusan a képernyõre - nyomtatja ki a további paramétereit).

A fentebb ismertetett definíciós forma az ún. modern stílus szerinti, a BORLAND C++ által is elfogadott alak. A hagyományos forma (classic style) a következõ:

tárolási osztály spec.  típus  azonosító( azonosítólista)

deklarációlista

blokk

Példaként álljon itt az elõbb definiált read függvény hagyományos formátumú definíciója:

           int read(handle, buf, len)
           int      handle;
           char     *buf;
           unsigned len;
           { 
              ...
           }
A hagyományos forma kicsit több gépelést kíván, talán kevésbé áttekinthetõ, de van egy nagy elõnye: sokkal szélesebb körben használható, mint a modern változat, ugyanis a klasszikus stílusú függvénydefiniciókat minden (régi) C-fordító képes "megérteni", míg a modern stílust csak az újabb fordítók támogatják. Ugyanakkor, ha egyes, tisztán C nyelven írt forrásmoduljainkat esetleg C++ környezetben is szeretnénk használni, feltétlenül a modern deklarációs stílust kell alkalmaznunk. Ennek okára a 2. fejezetben az ún. sokalakúság kapcsán világítunk rá. Hogy melyik stílust használjuk a függvények definiálására, illetve deklarálására, a programozónak kell eldöntenie. Ha széles körben portábilis programot szeretnénk írni, célszerû a hagyományos stílus, míg ha a jövõbe tekintünk, és C++ modulokkal közös project-ben szeretnénk hagyományos C modulokat is (átírás, modernizálás nélkül) felhasználni, a modern stílus alkalmazását javasoljuk. A kettõt nem mindig lehet keverni a BORLAND C++-ban. Ha C++ moduljaink is vannak a project-ben, akkor a BORLAND C++ fordító a hagyományos stílusú függvény definiciókat, illetve a formális paraméterlista nélküli deklarációkat nem fogadja el. A fenti dilemma feloldására célszerû a preprocesszor által nyújtotta feltételes fordítás lehetõségét kihasználni.

Mi e fejezet hátralévõ részében a modern formát használjuk példáinkban. Természetesen, ha a portabilitás érdekében régi C fordítókra is fel kell készülnünk, akkor célszerû a hagyományos alakot használni.

A pelda.c mintaprogramot tanulmányozva feltûnhet, hogy a main függvény által visszaadott érték típusáról (void) eddig még nem tettünk említést. Ennek az az oka, hogy a void éppen azt jelenti, hogy nem létezõ, nem definiált típusú tárolási egység. Vannak függvények, amelyek szerepe az, hogy meghívásukkor bizonyos feladatot végrehajtsanak, de az általuk visszaadott érték közömbös, érdektelen (más programozási nyelvekben ezeket a programegységeket szubrutinnak vagy eljárásnak nevezik). A void "típusú" függvénydeklaráció azt jelzi a fordítóprogramnak, hogy az adott függvény nem ad vissza értéket, és így az figyelmeztethet, ha tévedésbõl mégis felhasználjuk a függvény értékét. (A void "típus" egy másik jellemzõ használata a void* típusú mutatók deklarálása. A void* típussal általános pointereket deklarálhatunk (lásd 1.9.7-et): azt jellezzük ezen a módon, hogy pontosan még nem tudjuk, milyen típusú tárolási egység címérõl van szó, a pontos típus majd késõbb derül ki, és akkor a pointer típusát a megfelelõre konvertáljuk - lásd késõbb a típuskonverzió operátorát a 1.5.2 szakaszban.)

Ha egy más modulban definiált globális vagy könyvtári függvényt szeretnénk meghívni, függvénydeklarációt (function prototype) célszerû használni (sõt, a C++-ban ez egyenesen kötelezõ). A C nyelv megkötései ezen a téren nem olyan szigorúak, mint a változók esetén, ugyanis szabad meghívni nem deklarált függvényt, ha az int értéket ad vissza. Ezzel azonban kizárjuk azt a lehetõséget, hogy a fordítóprogram a paramétereket ellenõrizhesse. Javasolt ezért, hogy minden függvényt meghívása elõtt deklaráljunk. A szabványos könyvtári függvények deklarációi mind megtalálhatók a megfelelõ include file-okban, tehát ezek beépítésével a deklarációk automatikusan érvényre jutnak. A leggyakrabban használt stdio.h tartalmazza a fõbb be- és kiviteli függvények deklarációit (ezenkívül fontos struktúra- és szimbólum-definíciókat); a string.h a karakterláncok kezelését végzõ függvényekét; az stdlib.h az általános jellegû, a math.h a matematikai függvények deklarációit tartalmazza, egyéb hasznos definíciók mellett; stb.

A függvénydeklaráció formája nagyon hasonlít a definícióéra:

tárolási osztály  típus  azonosító( formális paraméterlista);

Látható, hogy elmarad a függvénytörzset alkotó blokk, és megjelenik a lezáró pontosvesszõ (;). A tárolási osztály lehet extern, vagy static, ha elmarad, az extern-t jelent. A read deklarációja - a modern stílusú definiciójához hasonlóan - tehát a következõ:

       extern int read(int handle, char *buf, unsigned len);
A függvénydeklaráció másik - a fentivel egyenértékû, de szélesebb körben (azaz régebbi C fordítóknál is) alkalmazható - formája:

tárolási osztály  típus  azonosító( típuslista);

A read példájánál maradva:

       extern int read(int, char *, unsigned);
A paraméterek helyén szereplõ típusdeklarációkat úgy kapjuk, hogy a teljes paraméter deklarációkból elhagyjuk az azonosítókat.

Lehetõség van arra is, hogy a teljes paraméterdeklarációt elhagyjuk, ekkor csupán azt közöljük a fordítóval, hogy az adott azonosító függvénynév, és milyen típust ad vissza. (Ez a megoldás igazodik a klasszikus stílusú függvénydefiniciókhoz, ugyanis a régebbi C fordítók sokszor nem fogadják el a deklarációban a paraméterlista megadását.) Ez minimálisan szükséges, ha a függvény nem int-et ad vissza. Ha egy függvény nem vár paramétert, azt a deklarációban úgy jelezzük, hogy a paraméterlista helyére a void kulcsszót írjuk, mint ahogy ezt a pelda.c-ben is tettük.


next up previous contents
Elõre: Módosító jelzõk Fel: Tárolási osztályokhatáskörök Vissza: Egyszerû adatdeklarátorok