Scope ( engelsk scope ) i programmering - en del af programmet , inden for hvilken identifikatoren er erklæret som navnet på en programenhed (normalt en variabel , datatype eller funktion)), forbliver forbundet med denne essens, det vil sige, tillader gennem sig selv at henvise til den. En objektidentifikator siges at være "synlig" et bestemt sted i programmet, hvis den kan bruges til at henvise til det givne objekt på det sted. Uden for scope kan den samme identifikator være knyttet til en anden variabel eller funktion, eller være fri (ikke forbundet med nogen af dem). Omfanget kan, men behøver ikke, være det samme som omfanget af det objekt, som navnet er knyttet til.
Identifikatorbinding ( engelsk binding ) i terminologien for nogle programmeringssprog er processen med at definere et programobjekt, hvortil adgang giver en identifikator på et bestemt sted i programmet og på et bestemt tidspunkt i dets udførelse. Dette koncept er i det væsentlige synonymt med scope , men kan være mere praktisk, når man overvejer nogle aspekter af programudførelse.
Scopes passer ind i hinanden og udgør et hierarki , fra et lokalt omfang, begrænset af en funktion (eller endda en del af det), til et globalt omfang, hvis identifikatorer er tilgængelige i hele programmet. Afhængigt af reglerne for et bestemt programmeringssprog kan scopes også implementeres på to måder: leksikalsk (statisk) eller dynamisk .
Scoping kan også give mening for markup-sprog : for eksempel i HTML er omfanget af et kontrolnavn form (HTML) fra <form> til </form> [1] .
I et monolitisk (enkelt-modul) program uden indlejrede funktioner og uden brug af OOP, kan der kun være to typer af omfang: global og lokal. Andre typer eksisterer kun, hvis der er visse syntaktiske mekanismer i sproget.
På OOP -sprog kan der udover ovenstående understøttes særlige omfangsbegrænsninger, som kun gælder for klassemedlemmer (identifikatorer, der er erklæret inden for klassen eller relateret til den):
I de enkleste tilfælde bestemmes omfanget af, hvor identifikatoren er deklareret. I de tilfælde, hvor erklæringens sted ikke entydigt kan angive omfanget, anvendes særlige justeringer.
Ovenstående liste udtømmer ikke alle nuancerne ved at definere omfanget, der kan være tilgængeligt i et bestemt programmeringssprog. Så for eksempel er forskellige fortolkninger af kombinationer af modulært omfang og erklæret synlighed af medlemmer af en OOP-klasse mulige. På nogle sprog (for eksempel C++) begrænser erklæringen om et privat eller beskyttet omfang for et klassemedlem adgang til det fra enhver kode, der ikke er relateret til dens klasses metoder. I andre (Object Pascal) er alle medlemmer af klassen, inklusive private og beskyttede, fuldt tilgængelige i det modul, hvori klassen er erklæret, og omfangsbegrænsninger gælder kun i andre moduler, der importerer denne.
Omfang i et program danner naturligt en lagdelt struktur, med nogle områder indlejret i andre. Hierarkiet af områder er normalt bygget på alle eller nogle niveauer fra sættet: "global - pakke - modulær - klasser - lokal" (den specifikke rækkefølge kan variere lidt på forskellige sprog).
Pakker og navnerum kan have flere niveauer af indlejring, så deres omfang bliver også indlejret. Forholdet mellem modul- og klasseomfang kan variere meget fra sprog til sprog. Lokale navnerum kan også indlejres, selv i tilfælde, hvor sproget ikke understøtter indlejrede funktioner og procedurer. Så der er for eksempel ingen indlejrede funktioner i C++-sproget, men hver sammensat sætning (som indeholder et sæt kommandoer omsluttet af krøllede klammer) danner sit eget lokale omfang, hvor det er muligt at erklære dets variable.
Den hierarkiske struktur giver mulighed for at løse tvetydigheder, der opstår, når den samme identifikator bruges i mere end én værdi i et program. Søgningen efter det ønskede objekt starter altid fra det område, hvori koden, der får adgang til identifikatoren, er placeret. Hvis der er et objekt med den ønskede identifikator i det givne omfang, så er det det objekt, der bruges. Hvis der ikke er nogen, fortsætter oversætteren søgningen blandt de identifikatorer, der er synlige i det omsluttende omfang, hvis det heller ikke er der, i det næste hierarkiniveau.
program Eksempel1 ; var a , b , c : Heltal ; (* Globale variabler. *) procedure f1 ; var b , c : Heltal (* Lokale variabler for procedure f1. *) begynder a := 10 ; (* Ændringer global a. *) b := 20 ; (* Ændrer lokal b. *) c := 30 ; (* Ændrer lokale c. *) writeln ( ' 4: ' , a , ',' , b , ',' , c ) ; ende ; procedure f2 ; var b , c : Heltal (* Lokale variabler for procedure f2. *) procedure f21 ; var c : Heltal (* Procedure lokal variabel f21. *) begynder a := 1000 ; (* Ændringer globalt a. *) b := 2000 ; (* Ændrer lokal b i procedure f2. *) c := 3000 ; (* Ændrer lokal c i procedure f21.*) writeln ( ' 5: ' , a , ',' , b , ',' , c ) ; ende ; begynde a := 100 ; (* Ændringer global a. *) b := 200 ; (* Ændrer lokal b. *) c := 300 ; (* Ændrer lokale c. *) writeln ( ' 6: ' , a , ',' , b , ',' , c ) ; f21 ; skrivln ( ' 7: ' , a , ',' , b , ',' , c ) ; ende ; begynde (* Initialisering af globale variabler. *) a := 1 ; b := 2 ; c := 3 ; skrivln ( ' 1: ' , a , ',' , b , ',' , c ) ; f1 ; skrivln ( ' 2: ' , a , ',' , b , ',' , c ) ; f2 ; skrivln ( ' 3: ' , a , ',' , b , ',' , c ) ; ende .Så når du kører ovenstående Pascal-program, får du følgende output:
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,3I en funktion er f1variabler bog ci det lokale omfang, så deres ændringer påvirker ikke globale variabler af samme navn. En funktion f21indeholder kun en variabel i sit lokale omfang c, så den ændrer både den globale aog bden lokale i den omsluttende funktion f2.
Brugen af lokale variabler – som har et begrænset omfang og kun eksisterer inden for den aktuelle funktion – hjælper med at undgå navngivningskonflikter mellem to variable med samme navn. Der er dog to meget forskellige tilgange til spørgsmålet om, hvad det vil sige at "være inde" i en funktion og følgelig to muligheder for at implementere lokalt omfang:
For "rene" funktioner, der kun opererer på deres egne parametre og lokale variable, er de leksikalske og dynamiske omfang altid de samme. Problemer opstår, når en funktion bruger eksterne navne, såsom globale variabler eller lokale variabler af funktioner, som den er en del af eller kaldet fra. Så hvis en funktion fkalder en funktion, der ikke er indlejret i den g, har funktionen med den leksikalske tilgang g ikke adgang til funktionens lokale variabler f. Med den dynamiske tilgang vil funktionen g dog have adgang til funktionens lokale variable f, fordi den gblev kaldt ved kørsel f.
Overvej for eksempel følgende program:
x = 1 funktion g () { echo $x ; x = 2 _ } funktion f () { lokal x = 3 ; g ; } f # udskriver 1 eller 3? echo $x # vil udsende 1 eller 2?Funktionen g()viser og ændrer værdien af variablen x, men denne variabel er g()hverken en parameter eller en lokal variabel, det vil sige, den skal være tilknyttet en værdi fra omfanget, der indeholder g(). Hvis det sprog, som programmet er skrevet på, bruger leksikalske omfang, skal navnet «x»indeni g()være forbundet med en global variabel x. Funktionen g()kaldet fra f()vil udskrive startværdien af den globale х , derefter ændre den, og den ændrede værdi vil blive udskrevet af den sidste linje i programmet. Det vil sige, at programmet viser først 1, derefter 2. Ændringer i den lokale xfunktion i funktionens tekst f()vil ikke påvirke dette output på nogen måde, da denne variabel hverken er synlig i det globale omfang eller i funktionen g().
Hvis sproget bruger dynamiske scopes, er navnet «x»internt g()forbundet med funktionens lokale variabel , da det kaldes indefra og går ind i dets scope. Her vil funktionen vise den lokale variabel for funktionen og ændre den, men dette vil ikke påvirke værdien af det globale x på nogen måde, så programmet viser først 3, derefter 1. Da programmet i dette tilfælde er skrevet i bash , som bruger en dynamisk tilgang, vil det i virkeligheden ske. xf()g() f()g()xf()
Både leksikalsk og dynamisk binding har deres fordele og ulemper. I praksis træffes valget mellem det ene og det andet af udvikleren ud fra både hans egne præferencer og karakteren af det programmeringssprog, der designes. De fleste typiske imperative sprog på højt niveau, der oprindeligt er designet til at bruge en compiler (i målplatformskoden eller i den virtuelle maskine-bytekode, det gør ikke noget), implementerer et statisk (leksikalsk) omfang, da det er mere bekvemt implementeret i compiler. Compileren arbejder med en leksikalsk kontekst, der er statisk og ikke ændrer sig under programafviklingen, og ved at behandle referencen til et navn, kan den nemt bestemme adressen i hukommelsen, hvor objektet, der er knyttet til navnet, befinder sig. Den dynamiske kontekst er ikke tilgængelig for compileren (da den kan ændre sig under programafvikling, fordi den samme funktion kan kaldes mange steder, og ikke altid eksplicit), så for at give dynamisk omfang, skal compileren tilføje dynamisk understøttelse af objektdefinition til den kode, som identifikatoren henviser til. Dette er muligt, men reducerer programmets hastighed, kræver yderligere hukommelse og komplicerer compileren.
I tilfælde af fortolkede sprog (for eksempel scripting ) er situationen fundamentalt anderledes. Fortolkeren behandler programteksten direkte på udførelsestidspunktet og indeholder interne udførelsesstøttestrukturer, herunder tabeller med variabel- og funktionsnavne med reelle værdier og objektadresser. Det er nemmere og hurtigere for tolken at udføre dynamisk linking (et simpelt lineært opslag i en identifikatortabel) end at holde styr på leksikalsk omfang hele tiden. Derfor understøtter fortolkede sprog oftere dynamisk navnebinding.
Inden for både den dynamiske og leksikalske tilgang til navnebinding kan der være nuancer forbundet med det særlige ved et bestemt programmeringssprog eller endda dets implementering. Som et eksempel kan du overveje to C-lignende programmeringssprog: JavaScript og Go . Sprogene er syntaktisk ret tætte og begge bruger leksikalsk rækkevidde, men adskiller sig ikke desto mindre i detaljerne i dens implementering.
Følgende eksempel viser to tekstmæssigt lignende kodestykker i JavaScript og Go. I begge tilfælde er en variabel scopeinitialiseret med strengen "global" erklæret i det globale omfang, og f()værdien af omfang udledes først i funktionen, derefter en lokal erklæring af en variabel med samme navn initialiseret med strengen "lokal" , og til sidst udledes værdien igen scope. Det følgende er det faktiske resultat af at udføre funktionen f()i hvert enkelt tilfælde.
JavaScript | gå |
---|---|
var scope = "global" ; funktion f () { advarsel ( omfang ); //? var scope = "lokal" ; alarm ( omfang ); } | var scope = "global" func f () { fmt . println ( omfang ) //? var scope = "lokal" fmt . println ( omfang ) } |
udefineret lokal |
globale lokale |
Det er let at se, at forskellen ligger i, hvilken værdi der vises på linjen markeret med en kommentar med et spørgsmålstegn.
En anden nuance i semantikken i det leksikalske omfang er tilstedeværelsen eller fraværet af den såkaldte "bloksynlighed", det vil sige evnen til at erklære en lokal variabel ikke kun inde i en funktion, procedure eller modul, men også inde i en separat blok af kommandoer (på C-lignende sprog - omgivet af krøllede parenteser {}). Det følgende er et eksempel på identisk kode på to sprog, der giver forskellige resultater af udførelse af funktionen f().
JavaScript | gå |
---|---|
funktion f () { var x = 3 ; alarm ( x ); for ( var i = 10 ; i < 30 ; i += 10 ) { var x = i ; alarm ( x ); } advarsel ( x ); //? } | func f () { var x = 3 fmt . Println ( x ) for i := 10 ; i < 30 ; i += 10 { var x = i fmt . println ( x ) } fmt . println ( x ) // ? } |
3 10 20 20 |
3 10 20 3 |
Forskellen er, hvilken værdi der vil blive udskrevet af den sidste sætning i funktionen f()markeret med et spørgsmålstegn i kommentaren.
Synligheden af en identifikator bør ikke sidestilles med eksistensen af den værdi, som identifikatoren er knyttet til. Forholdet mellem synligheden af et navn og eksistensen af et objekt påvirkes af programmets logik og objektets lagerklasse . Nedenfor er nogle typiske eksempler.