Лекция
Привет, Вы узнаете о том , что такое область видимости, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое область видимости , настоятельно рекомендую прочитать все из категории Информатика.
область видимости (англ. scope) в программировании — часть программы, в пределах которой идентификатор, объявленный как имя некоторой программной сущности (обычно — переменной, типа данных или функции), остается связанным с этой сущностью, то есть позволяет посредством себя обратиться к ней. Говорят, что идентификатор объекта «виден» в определенном месте программы, если в данном месте по нему можно обратиться к данному объекту. За пределами области видимости тот же самый идентификатор может быть связан с другой переменной или функцией, либо быть свободным (не связанным ни с какой из них). Область видимости может, но не обязана совпадать с областью существования объекта, с которым связано имя.
Cвязывание идентификатора (англ. binding) в терминологии некоторых языков программирования — процесс определения программного объекта, доступ к которому дает идентификатор в конкретном месте программы и в конкретный момент ее выполнения. Это понятие по сути синонимично области видимости, но может быть более удобно при рассмотрении некоторых аспектов выполнения программ.
Область видимости также может иметь смысл для языков разметки: например, в HTML областью видимости имени элемента управления является форма (HTML)
В монолитной (одномодульной) программе без вложенных функций и без использования ООП может существовать только два типа области видимости: глобальная и локальная. Прочие типы существуют только при наличии в языке определенных синтаксических механизмов.
В ООП-языках дополнительно к вышеперечисленным могут поддерживаться специальные ограничения области видимости, действующие только для членов классов (идентификаторов, объявленных внутри класса или относящихся к нему:
В простейших случаях область видимости определяется местом объявления идентификатора. В случаях, когда место объявления не может однозначно задать область видимости, применяются специальные уточнения.
Приведенный перечень не исчерпывает всех нюансов определения области видимости, которые могут иметься в конкретном языке программирования. Так, например, возможны различные толкования сочетаний модульной области видимости и объявленной видимости членов ООП-класса. В одних языках (например, C++) объявление личной или защищенной области видимости для члена класса ограничивает доступ к нему из любого кода, не относящегося к методам своего класса. В других (Object Pascal) все члены класса, в том числе личные и защищенные, полностью доступны в пределах того модуля, в котором объявлен класс, а ограничения области видимости действуют только в других модулях, импортирующих данный.
Области видимости в программе естественным образом составляют многоуровневую структуру, в которой одни области входят в состав других. Иерархия областей обычно строится на всех или некоторых уровнях из набора: «глобальная — пакетные — модульные — классов — локальные» (конкретный порядок может несколько отличаться в разных языках).
Пакеты и пространства имен могут иметь несколько уровней вложенности, соответственно, вложенными будут и их области видимости. Отношения областей видимости модулей и классов могут сильно отличаться в разных языках. Об этом говорит сайт https://intellect.icu . Локальные пространства имен также могут быть вложенными, причем даже в тех случаях, когда язык не поддерживает вложенные функции и процедуры. Так, например, в языке C++ вложенных функций нет, но каждый составной оператор (содержащий набор команд, заключенный в фигурные скобки) образует собственную локальную область видимости, в которой возможно объявление своих переменных.
Иерархическая структура позволяет разрешать неоднозначности, которые возникают, когда один и тот же идентификатор используется в программе более чем в одном значении. Поиск нужного объекта всегда начинается с той области видимости, в которой располагается обращающийся к идентификатору код. Если в данной области видимости находится объект с нужным идентификатором, то именно он и используется. Если такового нет, транслятор продолжает поиск среди идентификаторов, видимых в объемлющей области видимости, если его нет и там — в следующей по уровню иерархии.
program Example1; var a,b,c: Integer; (* Глобальные переменные. *) procedure f1; var b,c: Integer (* Локальные переменные процедуры f1. *) begin a := 10; (* Изменяет глобальную a. *) b := 20; (* Изменяет локальную b. *) c := 30; (* Изменяет локальную с. *) writeln(' 4: ', a, ',', b, ',', c); end; procedure f2; var b,c: Integer (* Локальные переменные процедуры f2. *) procedure f21; var c: Integer (* Локальная переменная процедуры f21. *) begin a := 1000; (* Изменяет глобальную a. *) b := 2000; (* Изменяет локальную b процедуры f2. *) c := 3000; (* Изменяет локальную c процедуры f21.*) writeln(' 5: ', a, ',', b, ',', c); end; begin a := 100; (* Изменяет глобальную a. *) b := 200; (* Изменяет локальную b. *) c := 300; (* Изменяет локальную c. *) writeln(' 6: ', a, ',', b, ',', c); f21; writeln(' 7: ', a, ',', b, ',', c); end; begin (* Инициализация глобальных переменных. *) a := 1; b := 2; c := 3; writeln(' 1: ', a, ',', b, ',', c); f1; writeln(' 2: ', a, ',', b, ',', c); f2; writeln(' 3: ', a, ',', b, ',', c); end.
Так, при запуске приведенной выше программы на языке Паскаль будет получен следующий вывод:
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
В функции f1 переменные b и c находятся в локальной области видимости, поэтому их изменения не затрагивают одноименные глобальные переменные. Функция f21 содержит в своей локальной области видимости только переменную c, поэтому она изменяет и глобальную a, и b, локальную в объемлющей функции f2.
Использование локальных переменных — имеющих ограниченную область видимости и существующих лишь внутри текущей функции — помогает избежать конфликта имен между двумя переменными с одинаковыми именами. Однако существует два очень разных подхода к вопросу о том, что значит «быть внутри» функции и, соответственно, два варианта реализации локальной области видимости:
Для «чистых» функций, которые оперируют только своими параметрами и локальными переменными, лексическая и динамическая области видимости всегда совпадают. Проблемы возникают, когда функция использует внешние имена, например, глобальные переменные или локальные переменные функций, в которые она входит или из которых вызывается. Так, если функция f вызывает не вложенную в нее функцию g, то при лексическом подходе функция g не имеет доступа к локальным переменным функции f. При динамическом же подходе функция g будет иметь доступ к локальным переменным функции f, поскольку g была вызвана во время работы f.
Например, рассмотрим следующую программу:
x=1 function g () { echo $x ; x=2 ; } function f () { local x=3 ; g ; } f # выведет 1 или 3? echo $x # выведет 1 или 2?
Функция g() выводит и изменяет значение переменной x, но эта переменная не является в g() ни параметром, ни локальной переменной, то есть она должна быть связана со значением из области видимости, в которую входит g(). Если язык, на котором написана программа, использует лексические области видимости, то имя «x» внутри g() должно быть связано с глобальной переменной x. Функция g(), вызванная из f(), выведет первоначальное значение глобальной х, после чего поменяет его, и измененное значение будет выведено последней строкой программы. То есть программа выведет сначала 1, затем 2. Изменения локальной x в тексте функции f() на этом выводе никак не отразятся, так как эта переменная не видна ни в глобальной области, ни в функции g().
Если же язык использует динамические области видимости, то имя «x» внутри g() связывается с локальной переменной x функции f(), поскольку g() вызывается изнутри f() и входит в ее область видимости. Здесь функция g() выведет локальную переменную x функции f() и изменит ее же, а на значении глобальной x все это никак не скажется, поэтому программа выведет сначала 3, затем 1. Поскольку в данном случае программа написана на bash, который использует динамический подход, в реальности именно так и произойдет.
И лексическое, и динамическое связывание имеют свои положительные и отрицательные стороны. Практически выбор между тем и другим разработчик делает исходя как из собственных предпочтений, так и из характера проектируемого языка программирования. Большинство типичных императивных языков высокого уровня, изначально рассчитанных на использование компилятора (в код целевой платформы или в байт-код виртуальной машины, не принципиально), реализуют статическую (лексическую) область видимости, так как она удобнее реализуется в компиляторе. Компилятор работает с лексическим контекстом, который статичен и не меняется при исполнении программы, и, обрабатывая обращение к имени, он может легко определить адрес в памяти, где располагается связанный с именем объект. Динамический контекст недоступен компилятору (так как он может меняться в ходе исполнения программы, ведь одна и та же функция может вызываться во множестве мест, причем не всегда явно), так что для обеспечения динамической области видимости компилятор должен добавить в код динамическую поддержку определения объекта, на который ссылается идентификатор. Это возможно, но снижает скорость работы программы, требует дополнительной памяти и усложняет компилятор.
В случае с интерпретируемыми языками (например, скриптовыми) ситуация принципиально иная. Интерпретатор обрабатывает текст программы непосредственно в момент исполнения и содержит внутренние структуры поддержки исполнения, в том числе таблицы имен переменных и функций с реальными значениями и адресами объектов. Интерпретатору проще и быстрее выполнить динамическое связывание (простым линейным поиском в таблице идентификаторов), чем постоянно отслеживать лексическую область видимости. Поэтому интерпретируемые языки чаще поддерживают динамическое связывание имен.
В рамках как динамического, так и лексического подхода к связыванию имен могут быть нюансы, связанные с особенностями конкретного языка программирования или даже его реализации. В качестве примера рассмотрим два Си-подобных языка программирования: JavaScript и Go. Языки синтаксически довольно близки и оба используют лексическую область видимости, но, тем не менее, различаются деталями ее реализации.
В следующем примере показаны два текстуально аналогичных фрагмента кода на JavaScript и Go. В обоих случаях в глобальной области видимости объявляется переменная scope, инициализированная строкой «global», а в функции f() сначала выполняется вывод значения scope, затем — локальное объявление переменной с тем же именем, инициализированное строкой «local», и, наконец, повторный вывод значения scope. Далее приведен реальный результат выполнения функции f() в каждом случае.
JavaScript | Go |
---|---|
var scope = "global"; function f() { alert(scope); // ? var scope = "local"; alert(scope); } |
var scope = "global" func f() { fmt.Println(scope) // ? var scope = "local" fmt.Println(scope) } |
undefined local |
global local |
Легко видеть, что разница заключается в том, какое значение выводится в строке, помеченной комментарием со знаком вопроса.
Еще один нюанс в семантике лексической области видимости — наличие или отсутствие так называемой «блочной видимости», то есть возможности объявить локальную переменную не только внутри функции, процедуры или модуля, но и внутри отдельного блока команд (в Си-подобных языках — заключенного в фигурные скобки {}). Далее приведен пример идентичного кода на двух языках, дающего разные результаты выполнения функции f().
JavaScript | Go |
---|---|
function f () { var x = 3; alert(x); for (var i = 10; i < 30; i+=10) { var x = i; alert(x); } alert(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 |
Разница проявляется в том, какое значение будет выведено последним оператором в функции f(), помеченным знаком вопроса в комментарии.
Не следует отождествлять видимость идентификатора с существованием значения, с которым данный идентификатор связан. На соотношение видимости имени и существования объекта влияет логика программы и класс памяти объекта. Далее несколько типичных примеров.
// Начинается глобальная область видимости. int countOfUser = 0; int main() { // С этого момента объявляется новая область видимости, в которой видна глобальная. int userNumber[10]; }
#include int a = 0; // глобальная переменная int main() { printf("%d", a); // будет выведено число 0 { int a = 1; // объявлена локальная переменная а, глобальная переменная a не видна printf("%d", a); // будет выведено число 1 { int a = 2; // еще локальная переменная в блоке, глобальная переменная a не видна, не видна и предыдущая локальная переменная printf("%d", a); // будет выведено число 2 } } }
примечание
В C++ изначально было сделано п — friend-функции
А дальше пошло хуже, но хоть как-то развивалось:
таким оразом, когда private var виден наружу, это именно «протечка», лучше называть вещи своими именами.
вероятно для этого, придумали, let и const .
Исследование, описанное в статье про область видимости, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое область видимости и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Информатика
Комментарии
Оставить комментарий
Информатика
Термины: Информатика