A hatókör ( angolul terjedő ) a programozásban a program egy része , amelyen belül egy programentitás (általában változó , adattípus vagy függvény ) neveként deklarált azonosító ehhez az entitáshoz társítva marad, azaz lehetővé teszi, hogy hogy önmagán keresztül hivatkozzon rá. Egy objektumazonosítót akkor mondunk "láthatónak" a program egy bizonyos helyén, ha az adott objektumra az adott helyen hivatkozni lehet. A hatókörön kívül ugyanaz az azonosító társítható egy másik változóhoz vagy függvényhez, vagy szabad (egyikhez sem társítható). A hatókör lehet, de nem kell, hogy azonos legyen annak az objektumnak a hatókörével, amelyhez a név hozzá van rendelve.
Az azonosító kötés ( angol kötés ) egyes programozási nyelvek terminológiájában egy programobjektum meghatározásának folyamata, amelyhez való hozzáférés a program egy adott helyén és a végrehajtás egy adott pillanatában azonosítót ad. Ez a fogalom lényegében a hatókör szinonimája , de kényelmesebb lehet, ha figyelembe vesszük a programvégrehajtás egyes szempontjait.
A hatókörök egymásba illeszkednek, és egy hierarchiát alkotnak , a lokális, egy funkcióval (vagy akár annak egy részével) korlátozott hatókörtől a globális hatókörig, amelynek azonosítói az egész programban elérhetők. Ezenkívül egy adott programozási nyelv szabályaitól függően a hatóköröket kétféleképpen lehet megvalósítani: lexikailag (statikusan) vagy dinamikusan .
A hatókör a jelölőnyelvek esetében is hasznos lehet : például a HTML -ben a vezérlőnév hatóköre form (HTML) <form>-tól </form>-ig [1] .
Egy beágyazott függvények és OOP használata nélküli monolitikus (egy modulos) programokban csak kétféle hatókör lehet: globális és lokális. Más típusok csak akkor léteznek, ha vannak bizonyos szintaktikai mechanizmusok a nyelvben.
Az OOP nyelvekben a fentieken túl speciális hatóköri korlátozások is támogathatók, amelyek csak az osztálytagokra vonatkoznak ( az osztályon belül deklarált vagy ahhoz kapcsolódó azonosítók):
A legegyszerűbb esetekben a hatókört az határozza meg, hogy hol van deklarálva az azonosító. Azokban az esetekben, amikor a nyilatkozat helye nem tudja egyértelműen meghatározni a hatályt, speciális finomításokat alkalmazunk.
A fenti lista nem meríti ki az adott programozási nyelvben elérhető hatókör meghatározásának minden árnyalatát. Így például a moduláris hatókör és az OOP osztály tagjainak deklarált láthatóságának kombinációinak különböző értelmezései lehetségesek. Egyes nyelveken (például C++) egy osztálytag privát vagy védett hatókörének deklarálása korlátozza a hozzáférést minden olyan kód számára, amely nem kapcsolódik az osztály metódusaihoz. Más esetekben (Object Pascal) az osztály minden tagja, beleértve a privátokat és a védetteket is, teljes mértékben elérhető abban a modulban, amelyben az osztály deklarálva van, és a hatókör-korlátozások csak az ezt importáló többi modulra vonatkoznak.
A hatókörök egy programban természetesen réteges struktúrát alkotnak, egyes hatókörök pedig beágyazódnak másokba. A területek hierarchiája általában a halmaz összes vagy néhány szintjén épül fel: "globális - csomag - moduláris - osztályok - helyi" (a konkrét sorrend kissé eltérhet a különböző nyelveken).
A csomagok és névterek több szintű beágyazást is tartalmazhatnak, így a hatókörük is beágyazódik. A modul és az osztály hatókörei közötti kapcsolat nyelvenként nagyon eltérő lehet. A helyi névterek is beágyazhatók, még akkor is, ha a nyelv nem támogatja a beágyazott függvényeket és eljárásokat. Így például a C++ nyelvben nincsenek beágyazott függvények, hanem minden összetett utasítás (amely kapcsos zárójelekbe zárt parancshalmazt tartalmaz) saját lokális hatókört alkot, amelyben lehetőség van a változóinak deklarálására.
A hierarchikus struktúra lehetővé teszi a kétértelműségek feloldását, amelyek akkor merülnek fel, ha ugyanazt az azonosítót egynél több értékben használják egy programban. A kívánt objektum keresése mindig abból a hatókörből indul, amelyben az azonosítóhoz hozzáférő kód található. Ha van egy objektum a kívánt azonosítóval az adott hatókörben, akkor ez az objektum kerül felhasználásra. Ha nincs, akkor a fordító a befoglaló hatókörben látható azonosítók között folytatja a keresést, ha ott sincs, akkor a következő hierarchiaszinten.
program Példa1 ; var a , b , c : Integer ; (* Globális változók. *) eljárás f1 ; var b , c : Integer (* Az f1 eljárás helyi változói. *) kezdődik a := 10 ; (* Globális változás a. *) b := 20 ; (* Változások helyi b. *) c := 30 ; (* Megváltoztatja a helyi c. *) writeln ( ' 4: ' , a , ',' , b , ',' , c ) ; vége ; eljárás f2 ; var b , c : Integer (* Az f2 eljárás helyi változói. *) procedúra f21 ; var c : Integer (* Eljárás helyi változó f21. *) begin a := 1000 ; (* Globális változások a. *) b := 2000 ; (* Megváltoztatja az f2 eljárás helyi b értékét. *) c := 3000 ; (* Megváltoztatja az f21 eljárás helyi c értékét.*) writeln ( ' 5: ' , a , ',' , b , ',' , c ) ; vége ; kezdődik a := 100 ; (* Globális változások a. *) b := 200 ; (* Változások helyi b. *) c := 300 ; (* Megváltoztatja a helyi c. *) writeln ( ' 6: ' , a , ',' , b , ',' , c ) ; f21 ; writeln ( ' 7: ' , a , ',' , b , ',' , c ) ; vége ; begin (* Globális változók inicializálása. *) a := 1 ; b := 2 ; c := 3 ; writeln ( ' 1: ' , a , ',' , b , ',' , c ) ; f1 ; writeln ( ' 2: ' , a , ',' , b , ',' , c ) ; f2 ; writeln ( ' 3: ' , a , ',' , b , ',' , c ) ; vége .Tehát a fenti Pascal program futtatásakor a következő kimenetet kapja:
1:1,2,3 4:10,20,30 2:10,2,3 6: 100 200 300 5: 1000,2000,3000 7: 1000,2000,300 3:1000,2,3Egy függvényben a és változók f1a lokális hatókörben vannak, így azok változásai nem érintik az azonos nevű globális változókat. Egy függvény csak egy változót tartalmaz a lokális hatókörében , így módosítja mind a globális , mind a lokális értéket a befoglaló függvényben . bcf21cabf2
A korlátozott hatókörű és csak az aktuális függvényen belül létező lokális változók használata segít elkerülni az elnevezési ütközéseket két azonos nevű változó között. Két nagyon eltérő megközelítés létezik azonban arra a kérdésre, hogy mit jelent egy funkcióban „belül lenni”, és ennek megfelelően két lehetőség van a helyi hatókör megvalósítására:
A "tiszta" függvények esetében, amelyek csak saját paramétereiken és helyi változóikon működnek, a lexikális és dinamikus hatókör mindig ugyanaz. Problémák merülnek fel, ha egy függvény külső neveket használ, például globális változókat vagy olyan függvények lokális változóit, amelyeknek része vagy amelyekből hívják. Tehát, ha egy függvény olyan függvényt fhív meg, amely nincs beágyazva g, akkor a lexikális megközelítéssel a függvény g nem fér hozzá a függvény lokális változóihoz f. A dinamikus megközelítéssel azonban a függvény g hozzáférhet a függvény helyi változóihoz f, mivel gfutási időben hívták meg f.
Vegyük például a következő programot:
x = 1 függvény g () { echo $x ; x = 2_ _ } function f () { local x = 3 ; g ; } f # 1-et vagy 3-at nyomtat? echo $x # 1-et vagy 2-t fog kiadni?A függvény g()megjeleníti és módosítja a változó értékét x, de ez a változó g()sem nem paraméter, sem nem lokális változó, vagyis hozzá kell rendelni a hatókörből származó értékhez, amely tartalmazza a -t g(). Ha a nyelv, amelyen a program íródott, lexikális hatókört használ, akkor a «x»benne lévő nevet egy globális változóhoz g()kell társítani . A from-ból meghívott függvény kiírja a global kezdeti értékét , majd megváltoztatja azt, és a megváltozott értéket a program utolsó sora írja ki. Vagyis a program először 1-et, majd 2 -t jelenít meg. A függvény szövegében a lokális függvény változásai ezt a kimenetet semmilyen módon nem befolyásolják, mivel ez a változó sem a globális hatókörben, sem a függvényben nem látható . xg()f() хxf()g()
Ha a nyelv dinamikus hatóköröket használ, akkor a név «x»belsőleg a függvény helyi változójához van g()társítva , mivel belülről hívják meg, és belép a hatókörébe. Itt a függvény a függvény lokális változóját fogja megjeleníteni és megváltoztatni, de ez semmilyen módon nem befolyásolja a globális x értékét, így a program először 3-at, majd 1-et jelenít meg. Mivel ebben az esetben a program íródik a dinamikus megközelítést alkalmazó bash -ban a valóságban pontosan ez fog megtörténni. xf()g() f()g()xf()
Mind a lexikális, mind a dinamikus kötésnek megvannak az előnyei és hátrányai. A gyakorlatban az egyik és a másik közötti választást a fejlesztő hozza meg mind saját preferenciái, mind a tervezett programozási nyelv jellege alapján. A legtöbb tipikus magas szintű imperatív nyelv, amelyet eredetileg fordító használatára terveztek (a célplatform kódjába vagy a virtuális gép bájtkódjába, mindegy), statikus (lexikális) hatókört valósítanak meg, mivel ez kényelmesebben valósítható meg a fordítóprogram. A fordító olyan lexikai kontextussal dolgozik, amely statikus, és nem változik a program végrehajtása során, és a névre mutató hivatkozás feldolgozásával könnyen meg tudja határozni azt a címet a memóriában, ahol a névhez tartozó objektum található. A dinamikus kontextus nem elérhető a fordító számára (mivel a program végrehajtása során változhat, mert ugyanaz a függvény sok helyen hívható, és nem mindig kifejezetten), ezért a dinamikus hatókör biztosítása érdekében a fordítónak dinamikus támogatást kell adnia az objektumdefinícióhoz arra a kódra, amelyre az azonosító utal. Ez lehetséges, de csökkenti a program sebességét, további memóriát igényel és bonyolítja a fordítót.
Az értelmezett nyelvek esetében (például szkript ) a helyzet alapvetően más. Az értelmező közvetlenül a végrehajtáskor dolgozza fel a programszöveget, és belső végrehajtást támogató struktúrákat tartalmaz, beleértve a változó- és függvénynevek táblázatait valós értékekkel és objektumcímekkel. Könnyebb és gyorsabb az értelmező számára a dinamikus linkelés (egy egyszerű lineáris keresés egy azonosító táblában) végrehajtása, mint a lexikai hatókör állandó nyomon követése. Ezért az értelmezett nyelvek gyakrabban támogatják a dinamikus névkötést.
A névkötés dinamikus és lexikális megközelítésén belül is előfordulhatnak árnyalatok, amelyek egy-egy programozási nyelv sajátosságaihoz vagy akár annak megvalósításához köthetők. Példaként vegyünk két C-szerű programozási nyelvet: a JavaScriptet és a Go . A nyelvek szintaktikailag meglehetősen közel állnak egymáshoz, és mindkettő lexikális hatókört használ, de ennek ellenére eltérnek a megvalósítás részleteiben.
A következő példa két szövegesen hasonló kódrészletet mutat be JavaScriptben és Goban. Mindkét esetben scopea "global" karakterlánccal inicializált változót deklarálják a globális hatókörben, és f()először a hatókör értékét vezetik le a függvényben, majd egy azonos nevű változó lokális deklarációját a "local" karakterlánccal inicializálják. , és végül a rendszer újra kikövetkezteti az értéket scope. f()Az alábbiakban minden esetben a függvény végrehajtásának tényleges eredménye látható .
JavaScript | megy |
---|---|
var hatókör = "global" ; function f () { alert ( hatókör ); // ? var hatókör = "helyi" ; riasztás ( hatókör ); } | var hatókör = "global" func f () { fmt . println ( hatókör ) //? var Score = "local" fmt . println ( hatókör ) } |
undefined local |
globális lokális |
Könnyen belátható, hogy a különbség abban rejlik, hogy a kérdőjellel ellátott megjegyzéssel megjelölt sorban milyen érték jelenik meg.
A lexikai hatókör szemantikájának egy másik árnyalata az úgynevezett "blokk láthatóság" megléte vagy hiánya, vagyis az a képesség, hogy egy lokális változót nemcsak függvényen, eljáráson vagy modulon belül deklaráljunk, hanem egy különálló blokkon belül is. parancsok (C-szerű nyelveken - zárójelek között {}). A következő példa azonos kódra két nyelven, különböző eredményeket adva a függvény végrehajtásához f().
JavaScript | megy |
---|---|
függvény f () { var x = 3 ; figyelmeztetés ( x ); for ( var i = 10 ; i < 30 ; i += 10 ) { var x = i ; figyelmeztetés ( x ); } figyelmeztetés ( x ); // ? } | func f () { var x = 3 fmt . Println ( x ) i : = 10 esetén ; i < 30 ; i += 10 { var x = i fmt . println ( x ) } fmt . println ( x ) // ? } |
3 10 20 20 |
3 10 20 3 |
f()A különbség abban van, hogy a megjegyzésben kérdőjellel jelölt függvény utolsó utasítása milyen értéket ad ki .
Az azonosító láthatóságát nem szabad egyenlővé tenni annak az értéknek a meglétével, amelyhez az azonosító társul. A név láthatósága és egy objektum létezése közötti kapcsolatot a program logikája és az objektum tárolási osztálya befolyásolja. Az alábbiakban néhány tipikus példát mutatunk be.