Zona de vizibilitate

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 30 ianuarie 2021; verificările necesită 9 modificări .

Scop ( în engleză  scope ) în programare  - o parte a programului , în cadrul căreia identificatorul , declarat ca numele unei entități de program (de obicei, o variabilă , un tip de date sau o funcție), rămâne legat de această esență, adică permite prin ea însăși să se refere la ea. Se spune că un identificator de obiect este „vizibil” într-un anumit loc din program dacă poate fi folosit pentru a se referi la obiectul dat în acel loc. În afara domeniului de aplicare, același identificator poate fi asociat cu o altă variabilă sau funcție, sau poate fi liber (nu este asociat cu niciuna dintre ele). Domeniul de aplicare poate, dar nu este necesar, să fie același cu domeniul de aplicare al obiectului căruia este asociat numele.

Legarea identificatorului ( legarea în engleză  ) în terminologia unor limbaje de programare  este procesul de definire a unui obiect de program, accesul căruia oferă un identificator într-un anumit loc din program și la un anumit moment al execuției acestuia. Acest concept este în esență sinonim cu domeniul de aplicare , dar poate fi mai convenabil atunci când luăm în considerare unele aspecte ale execuției programului.

Scopurile se încadrează unele în altele și alcătuiesc o ierarhie , de la un domeniu local, limitat de o funcție (sau chiar o parte a acesteia), până la un domeniu global, ai cărui identificatori sunt disponibili în tot programul. De asemenea, în funcție de regulile unui anumit limbaj de programare, domeniile pot fi implementate în două moduri: lexical (static) sau dinamic .

Scoping poate avea sens și pentru limbajele de marcare : de exemplu, în HTML , domeniul de aplicare al unui nume de control este form (HTML) de la <form> la </form> [1] .

Tipuri de domeniul de aplicare

Într-un program monolitic (cu un singur modul) fără funcții imbricate și fără utilizarea OOP, pot exista doar două tipuri de domeniu: global și local. Alte tipuri există doar dacă există anumite mecanisme sintactice în limbaj.

În limbile OOP , pe lângă cele de mai sus, pot fi acceptate restricții speciale care se aplică numai membrilor clasei (identificatori declarați în cadrul clasei sau înrudiți cu aceasta):

Metode de stabilire a domeniului

În cele mai simple cazuri, domeniul de aplicare este determinat de locul în care este declarat identificatorul. În cazurile în care locul declarației nu poate preciza în mod clar domeniul de aplicare, se aplică perfecționări speciale.

Lista de mai sus nu epuizează toate nuanțele definirii domeniului de aplicare care pot fi disponibile într-un anumit limbaj de programare. Deci, de exemplu, sunt posibile diferite interpretări ale combinațiilor de domeniul modular și vizibilitatea declarată a membrilor unei clase OOP. În unele limbi (de exemplu, C++), declararea unui domeniu privat sau protejat pentru un membru al clasei restricționează accesul la acesta din orice cod care nu are legătură cu metodele clasei sale. În altele (Object Pascal), toți membrii clasei, inclusiv cei privați și protejați, sunt pe deplin accesibili în cadrul modulului în care este declarată clasa, iar restricțiile de domeniu se aplică numai în alte module care o importă pe aceasta.

Ierarhie și dezambiguizare

Scopurile dintr-un program formează în mod natural o structură stratificată, cu unele domenii imbricate în altele. Ierarhia zonelor este de obicei construită la toate sau la unele niveluri din set: „global - pachet - modular - clase - local” (ordinea specifică poate varia ușor în diferite limbi).

Pachetele și spațiile de nume pot avea mai multe niveluri de imbricare, astfel încât domeniile lor vor fi, de asemenea, imbricate. Relația dintre modul și domeniul de aplicare al clasei poate varia foarte mult de la o limbă la alta. Spațiile de nume locale pot fi, de asemenea, imbricate, chiar și în cazurile în care limba nu acceptă funcții și proceduri imbricate. Deci, de exemplu, nu există funcții imbricate în limbajul C++, dar fiecare instrucțiune compusă (conținând un set de comenzi închise între acolade) își formează propriul domeniu de aplicare local, în care este posibil să-și declare variabilele.

Structura ierarhică permite rezolvarea ambiguităților care apar atunci când același identificator este utilizat în mai multe valori dintr-un program. Căutarea obiectului dorit începe întotdeauna de la sfera în care se află codul care accesează identificatorul. Dacă există un obiect cu identificatorul dorit în domeniul dat, atunci acel obiect este utilizat. Dacă nu există, traducătorul continuă căutarea printre identificatorii vizibili în sfera anexată, dacă nici nu există, în următorul nivel de ierarhie.

program Exemplu1 ; var a , b , c : Integer ; (* Variabile globale. *) procedura f1 ; var b , c : Integer (* Variabile locale ale procedurii f1. *) începe a := 10 ; (* Modificări globale a. *) b := 20 ; (* Modificări locale b. *) c := 30 ; (* Schimbă c. local *) writeln ( ' 4: ' , a , ',' , b , ',' , c ) ; sfârşitul ; procedura f2 ; var b , c : Integer (* Variabile locale ale procedurii f2. *) procedura f21 ; var c : Integer (* Variabila locala de procedura f21. *) begin a := 1000 ; (* Modificări globale a. *) b := 2000 ; (* Modifică localul b al procedurii f2. *) c := 3000 ; (* Schimbă c local al procedurii f21.*) writeln ( ' 5: ' , a , ',' , b , ',' , c ) ; sfârşitul ; începe a := 100 ; (* Modificări globale a. *) b := 200 ; (* Modificări locale b. *) c := 300 ; (* Schimbă c. local *) writeln ( ' 6: ' , a , ',' , b , ',' , c ) ; f21 ; scrieln ( ' 7: ' , a , ',' , b , ',' , c ) ; sfârşitul ; începe (* Inițializarea variabilelor globale. *) a := 1 ; b := 2 ; c := 3 ; scrieln ( ' 1: ' , a , ',' , b , ',' , c ) ; f1 ; scrieln ( ' 2: ' , a , ',' , b , ',' , c ) ; f2 ; scrieln ( ' 3: ' , a , ',' , b , ',' , c ) ; sfârşitul .

Deci, când rulați programul Pascal de mai sus, veți obține următoarea ieșire:

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

Într-o funcție, f1variabilele bși csunt în domeniul local, deci modificările lor nu afectează variabilele globale cu același nume. O funcţie f21conţine doar o variabilă în domeniul ei local c, deci modifică atât globalul, cât aşi blocalul în funcţia de încadrare f2.

Lexical vs. domenii dinamice

Utilizarea variabilelor locale – care au o sferă limitată și există doar în cadrul funcției curente – ajută la evitarea conflictelor de denumire între două variabile cu același nume. Cu toate acestea, există două abordări foarte diferite la întrebarea ce înseamnă „a fi în interiorul” unei funcții și, în consecință, două opțiuni pentru implementarea domeniului de aplicare local:

  • domeniul de aplicare lexical , sau domeniul de aplicare lexical ( îng .  domeniul lexical ), sau legarea lexicală (statică) ( legarea lexicală (statică) ): domeniul local al unei funcții este limitat la textul definiției acestei funcții (numele variabilei are o valoare în interiorul corpului funcției și este considerat nedefinit în afara acesteia). 
  • domeniul de aplicare dinamic sau contextul dinamic ( ing.  sfera dinamică ) sau legarea dinamică ( ing.  legarea dinamică ): domeniul local este limitat de timpul de execuție al funcției (numele este disponibil în timp ce funcția este în execuție și dispare când funcția returnează controlul codului care l-a numit) .

Pentru funcțiile „pure” care operează numai pe proprii parametri și variabile locale, domeniile lexicale și dinamice sunt întotdeauna aceleași. Problemele apar atunci când o funcție folosește nume externe, cum ar fi variabile globale sau variabile locale ale funcțiilor din care face parte sau din care este apelată. Deci, dacă o funcție fapelează o funcție care nu este imbricată în ea g, atunci cu abordarea lexicală, funcția g nu are acces la variabilele locale ale funcției f. Cu abordarea dinamică, însă, funcția g va avea acces la variabilele locale ale funcției, fdeoarece a gfost apelată în timpul execuției f.

De exemplu, luați în considerare următorul program:

x = 1 function g () { echo $x ; x = 2 _ } function f () { local x = 3 ; g ; } f # afișează 1 sau 3? echo $x # va scoate 1 sau 2?

Funcția g()afișează și modifică valoarea variabilei x, dar această variabilă nu este g()nici un parametru, nici o variabilă locală, adică trebuie să fie asociată cu o valoare din domeniul care conține g(). Dacă limba în care este scris programul folosește domenii lexicale, atunci numele «x»din interior g()trebuie să fie asociat cu o variabilă globalăx . Funcția g()apelată de la f()va tipări valoarea inițială a globalului х , apoi o va modifica, iar valoarea modificată va fi tipărită de ultima linie a programului. Adică, programul va afișa mai întâi 1, apoi 2. Modificările aduse xfuncției locale în textul funcției f()nu vor afecta în niciun fel această ieșire, deoarece această variabilă nu este vizibilă nici în domeniul global, nici în funcție g().

Dacă limbajul folosește domenii dinamice, atunci numele este asociat «x»intern cu variabila locală a funcției , deoarece este apelată din interior și intră în domeniul său. Aici, funcția va afișa variabila locală a funcției și o va modifica, dar acest lucru nu va afecta în niciun fel valoarea x-ului global, așa că programul va afișa mai întâi 3, apoi 1. Deoarece în acest caz programul este scris în bash , care utilizează o abordare dinamică, în realitate acest lucru se va întâmpla. g()xf()g() f()g()xf()

Atât legarea lexicală, cât și cea dinamică au avantajele și dezavantajele lor. În practică, alegerea între unul și altul este făcută de dezvoltator atât pe baza propriilor preferințe, cât și pe natura limbajului de programare proiectat. Majoritatea limbajelor imperative tipice de nivel înalt, concepute inițial pentru a utiliza un compilator (în codul platformei țintă sau în codul de octeți al mașinii virtuale, nu contează), implementează un domeniu static (lexical), deoarece este implementat mai convenabil în compilator. Compilatorul lucrează cu un context lexical care este static și nu se modifică în timpul execuției programului, iar prin procesarea referinței la un nume, poate determina cu ușurință adresa din memorie unde se află obiectul asociat numelui. Contextul dinamic nu este disponibil pentru compilator (deoarece se poate schimba în timpul execuției programului, deoarece aceeași funcție poate fi apelată în multe locuri și nu întotdeauna în mod explicit), așa că pentru a oferi un domeniu dinamic, compilatorul trebuie să adauge suport dinamic pentru definirea obiectului la codul, la care se referă identificatorul. Acest lucru este posibil, dar reduce viteza programului, necesită memorie suplimentară și complică compilatorul.

În cazul limbilor interpretate (de exemplu, scripting ), situația este fundamental diferită. Interpretul procesează textul programului direct în momentul execuției și conține structuri interne de suport pentru execuție, inclusiv tabele cu nume de variabile și funcții cu valori reale și adrese de obiect. Este mai ușor și mai rapid pentru interpret să efectueze legături dinamice (o simplă căutare liniară într-un tabel de identificare) decât să țină evidența domeniului lexical tot timpul. Prin urmare, limbile interpretate acceptă mai des legarea dinamică a numelui.

Caracteristici de legare a numelor

Atât în ​​abordarea dinamică, cât și în cea lexicală a legării numelor, pot exista nuanțe asociate cu particularitățile unui anumit limbaj de programare sau chiar cu implementarea acestuia. Ca exemplu, luați în considerare două limbaje de programare asemănătoare C: JavaScript și Go . Limbile sunt destul de apropiate din punct de vedere sintactic și ambele folosesc sfera lexicală, dar diferă totuși în detaliile implementării sale.

Începutul domeniului de aplicare al numelui local

Următorul exemplu arată două fragmente de cod similare din punct de vedere textual în JavaScript și Go. În ambele cazuri, o variabilă scopeinițializată cu șirul „global” este declarată în sfera globală, iar f()valoarea scope este mai întâi dedusă în funcție, apoi o declarație locală a unei variabile cu același nume inițializată cu șirul „local” , iar în final valoarea este re-inferită scope. Următorul este rezultatul real al executării funcției f()în fiecare caz.

JavaScript merge
var scope = "global" ; function f () { alert ( scope ); // ? var scope = "local" ; alertă ( sfera de aplicare ); } var scope = "global" func f () { fmt . println ( sfera de aplicare ) // ? var scope = "local" fmt . println ( sfera de aplicare ) }

local nedefinit

local global

Este ușor de observat că diferența constă în ce valoare este afișată pe linia marcată cu un comentariu cu un semn de întrebare.

  • În JavaScript , domeniul de aplicare al unei variabile locale este întreaga funcție , inclusiv partea din ea care este înainte de declarație; în acest caz , inițializarea acestei variabile se realizează numai în momentul prelucrării liniei în care se află. La momentul primului apel, alert(scope)domeniul de aplicare al variabilei locale există deja și este disponibil, dar nu a primit încă o valoare, adică, conform regulilor limbii, are o valoare specială undefined. De aceea, pe linia marcată va fi afișat „nedefinit”.
  • Go folosește o abordare mai tradițională pentru acest tip de limbaj, prin care domeniul de aplicare al unui nume începe pe linia în care este declarat. Prin urmare, în interiorul funcției f(), dar înainte de declararea unei variabile locale scope, această variabilă nu este disponibilă, iar comanda marcată cu semn de întrebare tipărește valoarea variabilei globalescope , adică „global”.

Blocați vizibilitatea

O altă nuanță în semantica domeniului lexical este prezența sau absența așa-numitei „vizibilitate a blocurilor”, adică capacitatea de a declara o variabilă locală nu numai în interiorul unei funcții, proceduri sau modul, ci și în interiorul unui bloc separat. de comenzi (în limbaje asemănătoare C - incluse între paranteze {}). Următorul este un exemplu de cod identic în două limbi, dând rezultate diferite ale executării funcției f().

JavaScript merge
funcția f () { var x = 3 ; alertă ( x ); pentru ( var i = 10 ; i < 30 ; i += 10 ) { var x = i ; alertă ( x ); } alertă ( x ); // ? } func f () { var x = 3 fmt . Println ( x ) pentru i := 10 ; i < 30 ; i += 10 { var x = i fmt . println ( x ) } fmt . println ( x ) // ? }
3
10
20
20
3
10
20
3

Diferența constă în ce valoare va fi afișată de ultima declarație din funcția f()marcată cu un semn de întrebare în comentariu.

  • JavaScript nu avea domeniul de aplicare al blocurilor (înainte de introducerea ES6), iar redeclararea unei variabile locale funcționează la fel ca o atribuire normală. Atribuirea de xvalori iîn interiorul buclei forschimbă singura variabilă locală xcare a fost declarată la începutul funcției. Prin urmare, după ce bucla se termină, variabila xreține ultima valoare care i-a fost atribuită în buclă. Această valoare este afișată ca rezultat.
  • În Go, un bloc de instrucțiuni formează un domeniu local, iar o variabilă declarată în interiorul unei bucle x este o variabilă nouă al cărei scop este doar corpul buclei; îl înlocuiește xpe cel declarat la începutul funcției. Această variabilă „dublu locală” primește o nouă valoare la fiecare trecere a buclei și este scoasă, dar modificările sale nu afectează variabila declarată în afara buclei x. După ce bucla se termină, variabila declarată în ea хîncetează să mai existe, iar prima xdevine din nou vizibilă. Valoarea sa rămâne aceeași și este afișată ca rezultat.

Vizibilitatea și existența obiectelor

Vizibilitatea unui identificator nu trebuie echivalată cu existența valorii cu care este asociat identificatorul. Relația dintre vizibilitatea unui nume și existența unui obiect este afectată de logica programului și de clasa de stocare a obiectului. Mai jos sunt câteva exemple tipice.

  • Pentru variabilele, pentru care memoria este alocată și eliberată dinamic (pe heap ), orice raport de vizibilitate și existență este posibil. O variabilă poate fi declarată și apoi inițializată, caz în care obiectul corespunzător numelui va apărea de fapt mai târziu decât intrarea în domeniul de aplicare. Dar obiectul poate fi creat în avans, stocat și apoi atribuit unei variabile, adică să apară mai devreme. La fel și cu ștergerea: după apelarea comenzii de ștergere pentru o variabilă asociată unui obiect dinamic, variabila în sine rămâne vizibilă, dar valoarea ei nu există, iar accesarea acesteia va duce la rezultate imprevizibile. Pe de altă parte, dacă comanda de ștergere nu este apelată, atunci obiectul din memoria dinamică poate continua să existe chiar și după ce variabila care se referă la acesta a ieșit din domeniul de aplicare.
  • Pentru variabilele locale cu o clasă de stocare statică (în C și C++), valoarea apare (logic) la momentul începerii programului. În acest caz, numele este în domeniu numai în timpul execuției funcției care le conține. Mai mult, în intervalele dintre funcții, valoarea este păstrată.
  • Variabilele automate (în terminologia C), create la intrarea într-o funcție și distruse la ieșire, există pentru perioada de timp în care numele lor este vizibil. Adică, pentru ei, vremurile de disponibilitate și existență pot fi considerate practic coincidente.

Exemple

Xi

// Începe domeniul global. int countOfUser = 0 ; int main () { // De acum înainte se declară un nou domeniu, în care este vizibil cel global. int userNumber [ 10 ]; } #include <stdio.h> int a = 0 ; // variabilă globală int main () { printf ( "%d" , a ); // va fi afișat numărul 0 { int a = 1 ; // este declarată variabila locală a, variabila globală a nu este vizibilă printf ( "%d" , a ); // va fi afișat numărul 1 { int a = 2 ; // încă o variabilă locală în bloc, variabila globală a nu este vizibilă, iar variabila locală anterioară nu este vizibilă printf ( "%d" , a ); // va fi afișat numărul 2 } } }

Note

  1. HTML Language Specification Archival copie din 4 decembrie 2012 la Wayback Machine , traducător: A. Piramidin, intuit.ru, ISBN 978-5-94774-648-8 , 17. Prelegere: Formulare.
  2. Domenii de aplicare . Preluat la 11 martie 2013. Arhivat din original la 26 noiembrie 2019.