Zona de visibilidad

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 30 de enero de 2021; las comprobaciones requieren 9 ediciones .

El alcance ( en inglés  scope ) en programación  es una parte del programa , dentro de la cual el identificador , declarado como el nombre de alguna entidad del programa (generalmente una variable , tipo de dato o función ), queda asociado a esta entidad, es decir, te permite referirse a él a través de sí mismo. Se dice que un identificador de objeto es "visible" en un lugar determinado del programa si puede usarse para referirse al objeto dado en ese lugar. Fuera del ámbito, un mismo identificador puede estar asociado a otra variable o función, o ser libre (no asociado a ninguna de ellas). El alcance puede, pero no necesariamente, ser el mismo que el alcance del objeto al que está asociado el nombre.

La vinculación de identificadores ( en inglés  binding ) en la terminología de algunos lenguajes de programación  es el proceso de definición de un objeto de programa, cuyo acceso otorga un identificador en un lugar específico del programa y en un momento específico de su ejecución. Este concepto es esencialmente sinónimo de alcance , pero puede ser más conveniente al considerar algunos aspectos de la ejecución del programa.

Los ámbitos encajan entre sí y forman una jerarquía , desde un ámbito local, limitado por una función (o incluso parte de ella), hasta un ámbito global, cuyos identificadores están disponibles a lo largo del programa. Además, dependiendo de las reglas de un lenguaje de programación en particular, los alcances se pueden implementar de dos maneras: léxicamente (estáticamente) o dinámicamente .

El ámbito también puede tener sentido para los lenguajes de marcado : por ejemplo, en HTML , el ámbito de un nombre de control es formulario (HTML) de <form> a </form> [1] .

Tipos de alcance

En un programa monolítico (de un solo módulo) sin funciones anidadas y sin el uso de OOP, solo puede haber dos tipos de alcance: global y local. Otros tipos existen solo si hay ciertos mecanismos sintácticos en el lenguaje.

En los lenguajes OOP , además de lo anterior, se pueden admitir restricciones de alcance especiales que se aplican solo a los miembros de la clase (identificadores declarados dentro de la clase o relacionados con ella):

Métodos de alcance

En los casos más simples, el alcance está determinado por el lugar donde se declara el identificador. En los casos en que el lugar de la declaración no pueda especificar sin ambigüedad el alcance, se aplican mejoras especiales.

La lista anterior no agota todos los matices de definir el alcance que puede estar disponible en un lenguaje de programación en particular. Entonces, por ejemplo, son posibles diferentes interpretaciones de combinaciones de alcance modular y visibilidad declarada de miembros de una clase OOP. En algunos lenguajes (por ejemplo, C++), declarar un ámbito privado o protegido para un miembro de la clase restringe el acceso a él desde cualquier código que no esté relacionado con los métodos de su clase. En otros (Object Pascal), todos los miembros de la clase, incluidos los privados y protegidos, son totalmente accesibles dentro del módulo en el que se declara la clase, y las restricciones de alcance se aplican solo en otros módulos que importan este.

Jerarquía y desambiguación

Los ámbitos de un programa forman naturalmente una estructura en capas, con algunos ámbitos anidados dentro de otros. La jerarquía de áreas suele construirse en todos o en algunos niveles del conjunto: "global - paquete - modular - clases - local" (el orden específico puede variar ligeramente en diferentes idiomas).

Los paquetes y los espacios de nombres pueden tener varios niveles de anidamiento, por lo que sus ámbitos también estarán anidados. La relación entre los ámbitos del módulo y de la clase puede variar mucho de un idioma a otro. Los espacios de nombres locales también se pueden anidar, incluso en los casos en que el idioma no admita funciones y procedimientos anidados. Entonces, por ejemplo, no hay funciones anidadas en el lenguaje C++, pero cada declaración compuesta (que contiene un conjunto de comandos encerrados entre llaves) forma su propio ámbito local, en el que es posible declarar sus variables.

La estructura jerárquica permite la resolución de ambigüedades que surgen cuando se utiliza el mismo identificador en más de un valor en un programa. La búsqueda del objeto deseado parte siempre del ámbito en el que se encuentra el código de acceso al identificador. Si hay un objeto con el identificador deseado en el ámbito dado, entonces es ese objeto el que se usa. Si no lo hay, el traductor continúa la búsqueda entre los identificadores visibles en el ámbito envolvente, si tampoco lo hay, en el siguiente nivel de jerarquía.

programa Ejemplo1 ; var a , b , c : Entero ; (* Variables globales. *) procedimiento f1 ; var b , c : Integer (* Variables locales del procedimiento f1. *) begin a := 10 ; (* Cambios globales a. *) b := 20 ; (* Cambia local b. *) c := 30 ; (* Cambia local c. *) writeln ( ' 4: ' , a , ',' , b , ',' , c ) ; fin ; procedimiento f2 ; var b , c : Integer (* Variables locales del procedimiento f2. *) procedimiento f21 ; var c : Integer (* Procedimiento variable local f21. *) begin a := 1000 ; (* Cambios globales a. *) b := 2000 ; (* Cambia el local b del procedimiento f2. *) c := 3000 ; (* Cambia el c local del procedimiento f21.*) writeln ( ' 5: ' , a , ',' , b , ',' , c ) ; fin ; comienza un := 100 ; (* Cambios globales a. *) b := 200 ; (* Cambia local b. *) c := 300 ; (* Cambia local c. *) writeln ( ' 6: ' , a , ',' , b , ',' , c ) ; f21 ; writeln ( ' 7: ' , a , ',' , b , ',' , c ) ; fin ; begin (* Inicialización de variables globales. *) a := 1 ; segundo := 2 ; c := 3 ; writeln ( ' 1: ' , a , ',' , b , ',' , c ) ; f1 ; writeln ( ' 2: ' , a , ',' , b , ',' , c ) ; f2 ; writeln ( ' 3: ' , a , ',' , b , ',' , c ) ; fin _

Entonces, cuando ejecute el programa Pascal anterior, obtendrá el siguiente resultado:

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

En una función, f1las variables by cestán en el ámbito local, por lo que sus cambios no afectan a las variables globales del mismo nombre. Una función f21contiene sólo una variable en su ámbito local c, por lo que modifica tanto la variable global acomo bla local en la función que la encierra f2.

Léxico vs. ámbitos dinámicos

El uso de variables locales, que tienen un alcance limitado y solo existen dentro de la función actual, ayuda a evitar conflictos de nombres entre dos variables con el mismo nombre. Sin embargo, hay dos enfoques muy diferentes a la pregunta de qué significa "estar dentro" de una función y, en consecuencia, dos opciones para implementar el alcance local:

  • alcance léxico , o alcance léxico ( ing.  alcance léxico ), o enlace léxico (estático) ( ing. enlace  léxico (estático) ): el alcance local de una función está limitado al texto de la definición de esta función (el nombre de la variable tiene un valor dentro del cuerpo de la función y se considera indefinido fuera de ella).
  • alcance dinámico , o contexto dinámico ( eng.  alcance dinámico ), o enlace dinámico ( ing.  enlace dinámico ): el alcance local está limitado por el tiempo de ejecución de la función (el nombre está disponible mientras se ejecuta la función y desaparece cuando la función devuelve el control al código que lo llamó).

Para las funciones "puras" que operan solo con sus propios parámetros y variables locales, los ámbitos léxico y dinámico son siempre los mismos. Los problemas surgen cuando una función utiliza nombres externos, como variables globales o variables locales de funciones de las que forma parte o desde las que se llama. Entonces, si una función fllama a una función que no está anidada en ella g, entonces con el enfoque léxico, la función g no tiene acceso a las variables locales de la función f. Sin embargo, con el enfoque dinámico, la función g tendrá acceso a las variables locales de la función fporque se gllamó en tiempo de ejecución f.

Por ejemplo, considere el siguiente programa:

x = 1 función g () { echo $x ; x = 2 _ } función f () { local x = 3 ; g ; } f # imprime 1 o 3? echo $x # generará 1 o 2?

La función g()muestra y cambia el valor de la variable x, pero esta variable no es g()ni un parámetro ni una variable local, es decir, debe estar asociada a un valor del ámbito que contiene g(). Si el idioma en el que está escrito el programa utiliza ámbitos léxicos, entonces el nombre «x»interior g()debe estar asociado con una variable globalx . La función g()llamada desde f()imprimirá el valor inicial de global х , luego lo cambiará, y el valor modificado se imprimirá en la última línea del programa. Es decir, el programa mostrará primero 1, luego 2. Los cambios en la xfunción local en el texto de la función f()no afectarán esta salida de ninguna manera, ya que esta variable no es visible ni en el ámbito global ni en la función g().

Si el lenguaje usa ámbitos dinámicos, entonces el nombre se asocia «x»internamente g()con la variable localx de la función f(), ya que g() se llama desde dentro f() y entra en su ámbito. Aquí, la función g()mostrará la variable local xde la función f()y la cambiará, pero esto no afectará el valor de la x global de ninguna manera, por lo que el programa mostrará primero 3, luego 1. Ya que en este caso el programa está escrito en bash , que utiliza un enfoque dinámico, en realidad esto sucederá exactamente.

Tanto el enlace léxico como el dinámico tienen sus pros y sus contras. En la práctica, la elección entre uno y otro la hace el desarrollador basándose tanto en sus propias preferencias como en la naturaleza del lenguaje de programación que se está diseñando. La mayoría de los lenguajes imperativos de alto nivel típicos, originalmente diseñados para usar un compilador (en el código de la plataforma de destino o en el código de bytes de la máquina virtual, no importa), implementan un alcance estático (léxico), ya que se implementa más convenientemente en el compilador. El compilador trabaja con un contexto léxico que es estático y no cambia durante la ejecución del programa, y ​​al procesar la referencia a un nombre, puede determinar fácilmente la dirección en la memoria donde reside el objeto asociado con el nombre. El contexto dinámico no está disponible para el compilador (ya que puede cambiar durante la ejecución del programa, porque se puede llamar a la misma función en muchos lugares, y no siempre de forma explícita), por lo que para proporcionar un alcance dinámico, el compilador debe agregar soporte dinámico para la definición de objetos. al código al que se refiere el identificador. Esto es posible, pero reduce la velocidad del programa, requiere memoria adicional y complica el compilador.

En el caso de los lenguajes interpretados (por ejemplo, scripting ), la situación es fundamentalmente diferente. El intérprete procesa el texto del programa directamente en el momento de la ejecución y contiene estructuras internas de apoyo a la ejecución, incluidas tablas de nombres de variables y funciones con valores reales y direcciones de objetos. Es más fácil y rápido para el intérprete realizar enlaces dinámicos (una simple búsqueda lineal en una tabla de identificadores) que realizar un seguimiento del alcance léxico todo el tiempo. Por lo tanto, los lenguajes interpretados suelen admitir la vinculación dinámica de nombres.

Funciones de vinculación de nombres

Dentro del enfoque tanto dinámico como léxico del enlace de nombres, puede haber matices asociados con las peculiaridades de un lenguaje de programación en particular o incluso con su implementación. Como ejemplo, considere dos lenguajes de programación tipo C: JavaScript y Go . Los lenguajes son sintácticamente bastante cercanos y ambos usan alcance léxico, pero sin embargo difieren en los detalles de su implementación.

Comienzo del alcance del nombre local

El siguiente ejemplo muestra dos fragmentos de código textualmente similares en JavaScript y Go. En ambos casos, una variable scopeinicializada con la cadena "global" se declara en el alcance global, y el f()valor del alcance se deduce primero en la función, luego una declaración local de una variable con el mismo nombre inicializada con la cadena "local" , y finalmente se vuelve a inferir el valor scope. El siguiente es el resultado real de ejecutar la función f()en cada caso.

JavaScript Vamos
var ámbito = "global" ; función f () { alerta ( alcance ); // ? var ámbito = "local" ; alerta ( alcance ); } var alcance = función "global" f () { fmt . println ( alcance ) //? var ámbito = "local" fmt . println ( alcance ) }

local indefinido
mundial
local

Es fácil ver que la diferencia radica en qué valor se muestra en la línea marcada con un comentario con un signo de interrogación.

  • En JavaScript , el alcance de una variable local es la función completa , incluida la parte anterior a la declaración; en este caso , la inicialización de esta variable se realiza únicamente al momento de procesar la línea donde se encuentra. En el momento de la primera llamada, la alert(scope)variable local scope ya existe y está disponible, pero aún no ha recibido un valor, es decir, según las reglas del lenguaje, tiene un valor especial undefined. Es por eso que se mostrará "indefinido" en la línea marcada.
  • Go utiliza un enfoque más tradicional para este tipo de lenguaje, en el que el alcance de un nombre comienza en la línea donde se declara. Por lo tanto, dentro de la función f(), pero antes de la declaración de una variable local scope, esta variable no está disponible, y el comando marcado con un signo de interrogación imprime el valor de la variable globalscope , es decir, "global".

Visibilidad del bloque

Otro matiz en la semántica del ámbito léxico es la presencia o ausencia de la llamada "visibilidad de bloque", es decir, la capacidad de declarar una variable local no solo dentro de una función, procedimiento o módulo, sino también dentro de un bloque separado. de comandos (en lenguajes tipo C, encerrados entre corchetes {}). El siguiente es un ejemplo de código idéntico en dos idiomas, dando diferentes resultados al ejecutar la función f().

JavaScript Vamos
función f () { var x = 3 ; alerta ( x ); para ( var i = 10 ; i < 30 ; i += 10 ) { var x = i ; alerta ( x ); } alerta ( x ); // ? } función f () { var x = 3 fmt . Println ( x ) para i := 10 ; yo < 30 ; yo += 10 { var x = yo fmt . println ( x ) } fmt . imprimir ( x ) // ? }
3
10
20
20
3
10
20
3

La diferencia está en qué valor generará la última declaración en la función f()marcada con un signo de interrogación en el comentario.

  • JavaScript no tenía alcance de bloque (antes de la introducción de ES6), y volver a declarar una variable local funciona como una asignación normal. La asignación de xvalores identro del ciclo forcambia la única variable local xque se declaró al comienzo de la función. Por lo tanto, después de que finaliza el bucle, la variable xconserva el último valor que se le asignó en el bucle. Este valor se muestra como resultado.
  • En Go, un bloque de instrucciones forma un ámbito local y una variable declarada dentro de un ciclo x es una variable nueva cuyo ámbito es solo el cuerpo del ciclo; anula xel declarado al comienzo de la función. Esta variable "doblemente local" obtiene un nuevo valor en cada paso del bucle y se emite, pero sus cambios no afectan a la variable declarada fuera del bucle x. Una vez que finaliza el bucle, la variable declarada en él хdeja de existir y la primera xvuelve a ser visible. Su valor sigue siendo el mismo y se muestra como resultado.

Visibilidad y existencia de objetos

La visibilidad de un identificador no debe equipararse con la existencia del valor con el que está asociado el identificador. La relación entre la visibilidad de un nombre y la existencia de un objeto se ve afectada por la lógica del programa y la clase de almacenamiento del objeto. A continuación se muestran algunos ejemplos típicos.

  • Para las variables, para las cuales la memoria se asigna y libera dinámicamente (en el montón ), es posible cualquier proporción de visibilidad y existencia. Una variable puede declararse y luego inicializarse, en cuyo caso el objeto correspondiente al nombre aparecerá después de la entrada de alcance. Pero el objeto se puede crear de antemano, almacenar y luego asignar a una variable, es decir, aparecer antes. Lo mismo con la eliminación: después de llamar al comando de eliminación de una variable asociada con un objeto dinámico, la variable en sí permanece visible, pero su valor no existe, y acceder a ella conducirá a resultados impredecibles. Por otro lado, si no se llama al comando de eliminación, entonces el objeto en la memoria dinámica puede continuar existiendo incluso después de que la variable que hace referencia a él haya quedado fuera del alcance.
  • Para las variables locales con una clase de almacenamiento estático (en C y C++), el valor aparece (lógicamente) en el momento en que se inicia el programa. En este caso, el nombre está en el alcance solo durante la ejecución de la función contenedora. Además, en los intervalos entre funciones, se conserva el valor.
  • Las variables automáticas (en terminología C), creadas al entrar en una función y destruidas al salir, existen durante el período de tiempo en que su nombre es visible. Es decir, para ellos, los tiempos de disponibilidad y existencia prácticamente pueden considerarse coincidentes.

Ejemplos

xi

// Comienza el ámbito global. int cuentaDeUsuario = 0 ; int principal () { // A partir de ahora se declara un nuevo ámbito, en el que se ve el global. int número de usuario [ 10 ]; } #incluir <stdio.h> int a = 0 ; // variable global int principal () { printf ( "%d" , a ); // se mostrará el número 0 { int a = 1 ; // se declara la variable local a, la variable global a no es visible printf ( "%d" , a ); // se mostrará el número 1 { int a = 2 ; // sigue siendo una variable local en el bloque, la variable global a no está visible y la variable local anterior no está visible printf ( "%d" , a ); // se mostrará el número 2 } } }

Notas

  1. Copia de archivo de especificación de lenguaje HTML fechada el 4 de diciembre de 2012 en Wayback Machine , traductor: A. Piramidin, intuit.ru, ISBN 978-5-94774-648-8 , 17. Conferencia: Formularios.
  2. Alcances . Consultado el 11 de marzo de 2013. Archivado desde el original el 26 de noviembre de 2019.