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.