Oblast viditelnosti

Aktuální verze stránky ještě nebyla zkontrolována zkušenými přispěvateli a může se výrazně lišit od verze recenzované 30. ledna 2021; kontroly vyžadují 9 úprav .

Rozsah ( anglicky  scope ) v programování  - část programu , ve které je identifikátor deklarovaný jako jméno nějaké entity programu (obvykle proměnná , datový typ nebo funkce) .), zůstává s touto podstatou spojen, to znamená, že prostřednictvím sebe umožňuje na ni odkazovat. Říká se, že identifikátor objektu je „viditelný“ na určitém místě v programu, pokud jej lze použít k odkazování na daný objekt na tomto místě. Mimo rozsah může být stejný identifikátor spojen s jinou proměnnou nebo funkcí nebo může být volný (není spojen s žádnou z nich). Rozsah může, ale nemusí být stejný jako rozsah objektu, ke kterému je název přidružen.

Vazba identifikátoru ( anglicky  binding ) v terminologii některých programovacích jazyků  je proces definování programového objektu, k němuž přístup dává identifikátor na konkrétním místě v programu a v konkrétním okamžiku jeho provádění. Tento koncept je v podstatě synonymem pro rozsah , ale může být výhodnější, když zvažujeme některé aspekty provádění programu.

Rozsahy do sebe zapadají a tvoří hierarchii , od lokálního rozsahu, omezeného funkcí (nebo dokonce její částí), až po globální rozsah, jehož identifikátory jsou dostupné v celém programu. V závislosti na pravidlech konkrétního programovacího jazyka lze také obory implementovat dvěma způsoby: lexikálně (staticky) nebo dynamicky .

Rozsah může mít smysl i pro značkovací jazyky : například v HTML je rozsah názvu ovládacího prvku form (HTML) od <form> do </form> [1] .

Typy rozsahů

V monolitickém (jednomodulovém) programu bez vnořených funkcí a bez použití OOP mohou existovat pouze dva typy rozsahu: globální a lokální. Jiné typy existují pouze tehdy, pokud v jazyce existují určité syntaktické mechanismy.

V jazycích OOP mohou být kromě výše uvedeného podporována speciální omezení rozsahu, která se vztahují pouze na členy třídy (identifikátory deklarované v rámci třídy nebo s ní související):

Metody stanovení rozsahu

V nejjednodušších případech je rozsah určen tím, kde je identifikátor deklarován. V případech, kdy místo prohlášení nemůže jednoznačně určit rozsah, se uplatňují zvláštní upřesnění.

Výše uvedený seznam nevyčerpává všechny nuance definování rozsahu, který může být dostupný v konkrétním programovacím jazyce. Takže jsou například možné různé interpretace kombinací modulárního rozsahu a deklarované viditelnosti členů třídy OOP. V některých jazycích (například C++) deklarování soukromého nebo chráněného rozsahu pro člena třídy omezuje přístup k němu z jakéhokoli kódu, který nesouvisí s metodami jeho třídy. V jiných (Object Pascal) jsou všichni členové třídy, včetně soukromých a chráněných, plně přístupní v rámci modulu, ve kterém je třída deklarována, a omezení rozsahu platí pouze v ostatních modulech, které tuto třídu importují.

Hierarchie a jednoznačnost

Rozsahy v programu přirozeně tvoří vrstvenou strukturu, přičemž některé obory jsou vnořeny do jiných. Hierarchie oblastí je obvykle postavena na všech nebo některých úrovních ze sady: "globální - balíček - modulární - třídy - místní" (konkrétní pořadí se může v různých jazycích mírně lišit).

Balíčky a jmenné prostory mohou mít několik úrovní vnoření, takže jejich obory budou také vnořené. Vztah mezi rozsahem modulu a třídy se může jazyk od jazyka značně lišit. Lokální jmenné prostory lze také vnořit, a to i v případech, kdy jazyk nepodporuje vnořené funkce a procedury. V jazyce C++ tedy například neexistují žádné vnořené funkce, ale každý složený příkaz (obsahující sadu příkazů uzavřených ve složených závorkách) tvoří svůj vlastní lokální rozsah, ve kterém je možné deklarovat jeho proměnné.

Hierarchická struktura umožňuje řešení nejednoznačností, které vznikají, když je stejný identifikátor použit ve více než jedné hodnotě v programu. Hledání požadovaného objektu vždy začíná od rozsahu, ve kterém se nachází kód přistupující k identifikátoru. Pokud v daném rozsahu existuje objekt s požadovaným identifikátorem, pak je použit právě tento objekt. Pokud žádný není, překladatel pokračuje v hledání mezi identifikátory viditelnými v přiloženém oboru, pokud tam také není, na další úrovni hierarchie.

program Příklad1 ; var a , b , c : celé číslo ; (* Globální proměnné. *) postup f1 ; var b , c : Celé číslo (* Lokální proměnné procedury f1. *) begin a := 10 ; (* Změní globální a. *) b := 20 ; (* Změní místní b. *) c := 30 ; (* Změní místní c. *) writeln ( ' 4: ' , a , ',' , b , ',' , c ) ; konec ; postup f2 ; var b , c : Celé číslo (* Lokální proměnné procedury f2. *) procedure f21 ; var c : Celé číslo (* Lokální proměnná procedury f21. *) begin a := 1000 ; (* Globální změny a. *) b := 2000 ; (* Změní místní b procedury f2. *) c := 3000 ; (* Změní místní c procedury f21.*) writeln ( ' 5: ' , a , ',' , b , ',' , c ) ; konec ; begin a := 100 ; (* Změní globální a. *) b := 200 ; (* Změní místní b. *) c := 300 ; (* Změní místní c. *) writeln ( ' 6: ' , a , ',' , b , ',' , c ) ; f21 ; writeln ( ' 7: ' , a , ',' , b , ',' , c ) ; konec ; begin (* Inicializace globálních proměnných. *) a := 1 ; b := 2 ; c := 3 ; writeln ( ' 1: ' , a , ',' , b , ',' , c ) ; f1 ; writeln ( ' 2: ' , a , ',' , b , ',' , c ) ; f2 ; writeln ( ' 3: ' , a , ',' , b , ',' , c ) ; konec .

Když tedy spustíte výše uvedený program Pascal, získáte následující výstup:

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,3

Ve funkci jsou f1proměnné ba cv lokálním rozsahu, takže jejich změny neovlivňují stejnojmenné globální proměnné. Funkce f21obsahuje ve svém lokálním rozsahu pouze proměnnou c, takže v ohraničující funkci modifikuje jak globální, atak lokální . bf2

Lexikální vs. dynamické rozsahy

Použití lokálních proměnných – které mají omezený rozsah a existují pouze v rámci aktuální funkce – pomáhá vyhnout se konfliktům názvů mezi dvěma proměnnými se stejným názvem. Existují však dva velmi odlišné přístupy k otázce, co to znamená „být uvnitř“ funkce, a tedy dvě možnosti implementace místního rozsahu:

  • lexikální rozsah , nebo lexikální rozsah ( angl.  lexical scope ), nebo lexikální (statická) vazba ( angl.  lexical (static) binding ): lokální rozsah funkce je omezen na text definice této funkce (název proměnné má hodnotu uvnitř těla funkce a mimo něj se považuje za nedefinovanou).
  • dynamický rozsah , nebo dynamický kontext ( eng.  dynamic scope ) nebo dynamická vazba ( eng.  dynamic binding ): lokální rozsah je omezen dobou provádění funkce (název je dostupný, když je funkce vykonávána, a zmizí, když funkce vrátí řízení kódu, který jej volal) .

U „čistých“ funkcí, které fungují pouze na svých vlastních parametrech a lokálních proměnných, jsou lexikální a dynamické rozsahy vždy stejné. Problémy nastávají, když funkce používá externí názvy, jako jsou globální proměnné nebo lokální proměnné funkcí, kterých je součástí nebo z nichž je volána. Pokud tedy funkce fvolá funkci, která v ní není vnořená g, pak s lexikálním přístupem funkce g nemá přístup k lokálním proměnným funkce f. S dynamickým přístupem však g bude mít funkce přístup k místním proměnným funkce, fprotože gbyla volána za běhu f.

Zvažte například následující program:

x = 1 funkce g () { echo $x ; x = 2 _ } funkce f () { local x = 3 ; g ; } f # vytiskne 1 nebo 3? echo $x # bude výstup 1 nebo 2?

Funkce g()zobrazuje a mění hodnotu proměnné x, ale tato proměnná není g()ani parametr, ani lokální proměnná, to znamená, že musí být spojena s hodnotou z rozsahu, který obsahuje g(). Pokud jazyk, ve kterém je program napsán, používá lexikální rozsahy, pak název «x»uvnitř g()musí být spojen s globální proměnnou x. Funkce g()volaná z f()vypíše počáteční hodnotu global х , pak ji změní a změněná hodnota bude vytištěna na posledním řádku programu. To znamená, že program zobrazí nejprve 1, pak 2. Změny lokální xfunkce v textu funkce f()tento výstup nijak neovlivní, protože tato proměnná není viditelná ani v globálním rozsahu, ani ve funkci g().

Pokud jazyk používá dynamické rozsahy, pak je název «x»interně g()spojen s lokální proměnnou xfunkce f(), protože g() je volána zevnitř f() a vstupuje do jejího rozsahu. Zde funkce g()zobrazí lokální proměnnou xfunkce f()a změní ji, ale to nijak neovlivní hodnotu globálního x, takže program zobrazí nejprve 3, poté 1. Protože v tomto případě je program napsán v bash , který používá dynamický přístup, se to ve skutečnosti přesně stane.

Jak lexikální, tak dynamická vazba má svá pro a proti. V praxi výběr mezi jedním a druhým provádí vývojář na základě svých vlastních preferencí a povahy navrhovaného programovacího jazyka. Většina typických imperativních jazyků na vysoké úrovni, původně navržených pro použití kompilátoru (do kódu cílové platformy nebo do bytekódu virtuálního stroje, na tom nezáleží), implementuje statický (lexikální) rozsah, protože je pohodlněji implementován v kompilátor. Kompilátor pracuje s lexikálním kontextem, který je statický a během provádění programu se nemění, a zpracováním odkazu na jméno může snadno určit adresu v paměti, kde se nachází objekt spojený se jménem. Dynamický kontext není pro kompilátor k dispozici (protože se může během provádění programu změnit, protože stejnou funkci lze volat na mnoha místech a ne vždy explicitně), takže pro zajištění dynamického rozsahu musí kompilátor přidat dynamickou podporu pro definici objektu ke kódu, ke kterému identifikátor odkazuje. To je možné, ale snižuje to rychlost programu, vyžaduje další paměť a komplikuje kompilátor.

V případě interpretovaných jazyků (například skriptování ) je situace zásadně odlišná. Interpret zpracovává text programu přímo v okamžiku provádění a obsahuje vnitřní podpůrné struktury pro provádění, včetně tabulek názvů proměnných a funkcí s reálnými hodnotami a adresami objektů. Pro interpreta je jednodušší a rychlejší provádět dynamické spojování (jednoduché lineární vyhledávání v tabulce identifikátorů), než neustále sledovat lexikální rozsah. Proto interpretované jazyky častěji podporují dynamickou vazbu jmen.

Funkce vazby názvu

V rámci dynamického i lexikálního přístupu k vazbě jmen mohou existovat nuance spojené se zvláštnostmi konkrétního programovacího jazyka nebo dokonce s jeho implementací. Jako příklad uvažujme dva programovací jazyky podobné C: JavaScript a Go . Jazyky jsou si syntakticky docela blízké a oba používají lexikální rozsah, ale přesto se liší v detailech jeho implementace.

Začátek oboru místního názvu

Následující příklad ukazuje dva textově podobné fragmenty kódu v JavaScriptu a Go. V obou případech je v globálním rozsahu deklarována proměnná scopeinicializovaná řetězcem „global“ a f()ve funkci je nejprve odvozena hodnota oboru, poté lokální deklarace proměnné se stejným názvem inicializovaná řetězcem „local“ a nakonec je hodnota znovu odvozena scope. Níže je uveden skutečný výsledek provedení funkce f()v každém případě.

JavaScript Jít
var rozsah = "globální" ; funkce f () { upozornění ( rozsah ); // ? var rozsah = "místní" ; výstraha ( rozsah ); } var scope = "globální" func f () { fmt . println ( rozsah ) // ? var scope = "místní" fmt . println ( rozsah ) }
nedefinované
místní
globální
místní

Je snadné vidět, že rozdíl spočívá v tom, jaká hodnota je zobrazena na řádku označeném komentářem s otazníkem.

  • V JavaScriptu je rozsahem lokální proměnné celá funkce , včetně její části, která je před deklarací; v tomto případě se inicializace této proměnné provádí až v době zpracování řádku, kde se nachází. V době prvního volání alert(scope)již rozsah lokální proměnné existuje a je dostupný, ale ještě nezískal hodnotu, to znamená, že podle pravidel jazyka má speciální hodnotu undefined. Proto se v označeném řádku zobrazí "nedefinováno".
  • Go používá pro tento typ jazyka tradičnější přístup, přičemž rozsah názvu začíná na řádku, kde je deklarován. Proto uvnitř funkce f(), ale před deklarací lokální proměnné scope, tato proměnná není dostupná a příkaz označený otazníkem vypíše hodnotu globální proměnné scope, tedy "global".

Viditelnost bloku

Další nuancí v sémantice lexikálního rozsahu je přítomnost nebo nepřítomnost takzvané „viditelnosti bloku“, tedy schopnosti deklarovat lokální proměnnou nejen uvnitř funkce, procedury nebo modulu, ale také uvnitř samostatného bloku. příkazů (v jazycích podobných C - uzavřeno ve složených závorkách {}). Následuje příklad identického kódu ve dvou jazycích, který poskytuje různé výsledky při provádění funkce f().

JavaScript Jít
funkce f () { var x = 3 ; upozornění ( x ); for ( var i = 10 ; i < 30 ; i += 10 ) { var x = i ; upozornění ( x ); } upozornění ( x ); // ? } func f () { var x = 3 fmt . Println ( x ) pro i := 10 ; i < 30 ; i += 10 { var x = i fmt . println ( x ) } fmt . println ( x ) // ? }
3
10
20
20
3
10
20
3

Rozdíl je v tom, jakou hodnotu vypíše poslední příkaz ve funkci f()označené otazníkem v komentáři.

  • JavaScript neměl rozsah bloku (před zavedením ES6) a opětovné deklarování lokální proměnné funguje stejně jako normální přiřazení. Přiřazení xhodnot iuvnitř smyčky forzmění jedinou lokální proměnnou x, která byla deklarována na začátku funkce. Po skončení cyklu si tedy proměnná xuchová poslední hodnotu, která jí byla v cyklu přiřazena. Tato hodnota se zobrazí jako výsledek.
  • V Go tvoří blok příkazů lokální rozsah a proměnná deklarovaná uvnitř smyčky x je nová proměnná, jejíž rozsah je pouze tělem smyčky; přepíše xtu deklarovanou na začátku funkce. Tato "dvojitě lokální" proměnná získává novou hodnotu v každém průchodu smyčkou a je vydávána, ale její změny neovlivňují proměnnou deklarovanou mimo smyčku x. Po skončení cyklu хpřestane proměnná v něm deklarovaná existovat a první xse opět stane viditelnou. Jeho hodnota zůstane stejná a jako výsledek se zobrazí.

Viditelnost a existence objektů

Viditelnost identifikátoru by neměla být ztotožňována s existencí hodnoty, se kterou je identifikátor spojen. Vztah mezi viditelností jména a existencí objektu je ovlivněn logikou programu a třídou úložiště objektu. Níže jsou uvedeny některé typické příklady.

  • Pro proměnné, pro které je paměť alokována a uvolňována dynamicky (na haldě ), je možný jakýkoli poměr viditelnosti a existence. Proměnná může být deklarována a poté inicializována, v takovém případě se objekt odpovídající názvu skutečně objeví později než položka oboru. Objekt však může být vytvořen předem, uložen a poté přiřazen k proměnné, to znamená, že se objeví dříve. Totéž s mazáním: po zavolání příkazu delete pro proměnnou přidruženou k dynamickému objektu zůstane samotná proměnná viditelná, ale její hodnota neexistuje a přístup k ní povede k nepředvídatelným výsledkům. Na druhou stranu, pokud není zavolán příkaz delete, pak objekt v dynamické paměti může nadále existovat i poté, co proměnná, která na něj odkazuje, odešla mimo rozsah.
  • U lokálních proměnných se statickou třídou úložiště (v C a C++) se hodnota objeví (logicky) v době spuštění programu. V tomto případě je název v rozsahu pouze během provádění obsahující funkce. Navíc v intervalech mezi funkcemi je hodnota zachována.
  • Automatické (v terminologii C) proměnné, vytvořené při vstupu do funkce a zničené při výstupu, existují po dobu, kdy je jejich jméno viditelné. To znamená, že pro ně lze prakticky považovat doby dostupnosti a existence za shodné.

Příklady

Xi

// Spustí se globální rozsah. int pocetUzivatele = 0 ; int main () { // Od této chvíle je deklarován nový rozsah, ve kterém je viditelný ten globální. int userNumber [ 10 ]; } #include <stdio.h> int a = 0 ; // globální proměnná int main () { printf ( "%d" , a ); // zobrazí se číslo 0 { int a = 1 ; // lokální proměnná a je deklarována, globální proměnná a není viditelná printf ( "%d" , a ); // zobrazí se číslo 1 { int a = 2 ; // stále lokální proměnná v bloku, globální proměnná a není viditelná a předchozí lokální proměnná není viditelná printf ( "%d" , a ); // zobrazí se číslo 2 } } }

Poznámky

  1. Specifikace jazyka HTML Archivní kopie ze dne 4. prosince 2012 na Wayback Machine , překladatel: A. Piramidin, intuit.ru, ISBN 978-5-94774-648-8 , 17. Přednáška: Formuláře.
  2. Rozsahy . Získáno 11. března 2013. Archivováno z originálu dne 26. listopadu 2019.