Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Область видимости (англ. scope) в программировании

Лекция



Привет, Вы узнаете о том , что такое область видимости, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое область видимости , настоятельно рекомендую прочитать все из категории Информатика.

область видимости (англ. scope) в программировании — часть программы, в пределах которой идентификатор, объявленный как имя некоторой программной сущности (обычно — переменной, типа данных или функции), остается связанным с этой сущностью, то есть позволяет посредством себя обратиться к ней. Говорят, что идентификатор объекта «виден» в определенном месте программы, если в данном месте по нему можно обратиться к данному объекту. За пределами области видимости тот же самый идентификатор может быть связан с другой переменной или функцией, либо быть свободным (не связанным ни с какой из них). Область видимости может, но не обязана совпадать с областью существования объекта, с которым связано имя.

Cвязывание идентификатора (англ. binding) в терминологии некоторых языков программирования — процесс определения программного объекта, доступ к которому дает идентификатор в конкретном месте программы и в конкретный момент ее выполнения. Это понятие по сути синонимично области видимости, но может быть более удобно при рассмотрении некоторых аспектов выполнения программ.

Область видимости также может иметь смысл для языков разметки: например, в HTML областью видимости имени элемента управления является форма (HTML) Область видимости (англ. scope) в программировании

Область видимости (англ. scope) в программировании

Типы области видимости

В монолитной (одномодульной) программе без вложенных функций и без использования ООП может существовать только два типа области видимости: глобальная и локальная. Прочие типы существуют только при наличии в языке определенных синтаксических механизмов.

  • Глобальная область видимости — идентификатор доступен во всем тексте программы (во многих языках действует ограничение — только в тексте, находящемся после объявления этого идентификатора).
  • Локальная область видимости — идентификатор доступен только внутри определенной функции (процедуры).
  • Видимость в пределах модуля может существовать в модульных программах, состоящих из нескольких отдельных фрагментов кода, обычно находящихся в разных файлах. Идентификатор, чьей областью видимости является модуль, доступен из любого кода в пределах данного модуля.
  • Пакет или пространство имен. В глобальной области видимости искусственно выделяется поименованная подобласть. Имя «привязывается» к этой части программы и существует только внутри нее. Вне данной области имя либо вообще недоступно, либо доступно ограниченно.

В ООП-языках дополнительно к вышеперечисленным могут поддерживаться специальные ограничения области видимости, действующие только для членов классов (идентификаторов, объявленных внутри класса или относящихся к нему:

  • Приватная (личная, закрытая) (англ. private) область видимости означает, что имя доступно только внутри методов своего класса.
  • Защищенная (англ. protected) область видимости означает, что имя доступно только внутри своего класса и его классов-потомков.
  • Общая (англ. public) область видимости означает, что имя доступно в пределах области видимости, к которой относится его класс.

Способы задания области видимости

В простейших случаях область видимости определяется местом объявления идентификатора. В случаях, когда место объявления не может однозначно задать область видимости, применяются специальные уточнения.

  • Идентификатор, объявленный вне любого определения функции, процедуры, типа, является глобальным.
  • Идентификатор, объявленный внутри определения функции, является локальным в данной функции, то есть его областью видимости является эта функция.
  • Идентификатор, являющийся частью определения типа данных, в отсутствие дополнительных уточнений имеет ту же область видимости, что и идентификатор типа, в определение которого он входит.
  • В языках, поддерживающих модули, пакеты или пространства имен идентификатор, объявленный вне всех процедур и классов, по умолчанию относится к модулю, пакету или пространству имен, внутри которого находится его объявление. Сами пределы области видимости для пакета или пространства имен указываются с помощью специальных описаний, а модульная область видимости ограничивается обычно текущим файлом исходного текста программы. Особенностью этого типа видимости является то, что язык, как правило, содержит средства, позволяющие сделать идентификатор доступным и вне своего модуля (пакета или пространства имен), то есть «расширить» его область видимости. Для этого должно иметься сочетание двух факторов: содержащий идентификатор модуль должен быть импортирован с помощью специальной команды там, где предполагается его использование, а сам идентификатор при его описании должен быть дополнительно объявлен экспортируемым. Способы объявления идентификатора экспортируемым могут быть различны. Это могут быть специальные команды или модификаторы в описаниях, соглашения об именовании (например, в языке Go экспортируемыми являются идентификаторы пакетной области видимости, начинающиеся на заглавную букву). В ряде языков каждый модуль (пакет) искусственно делится на две части: раздел определений и раздел реализации, которые могут находиться как в пределах одного файла исходного кода (например, в Delphi), так и в разных (например, в языке Модула-2); экспортируемыми являются идентификаторы, объявленные в модуле определений.
  • Область видимости идентификатора, объявленного внутри ООП-класса, по умолчанию является либо приватной, либо общей. Иная область видимости придается с помощью специального описания (например, в C++ это модификаторы private, public, protected) .

Приведенный перечень не исчерпывает всех нюансов определения области видимости, которые могут иметься в конкретном языке программирования. Так, например, возможны различные толкования сочетаний модульной области видимости и объявленной видимости членов ООП-класса. В одних языках (например, 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.

Лексические vs. динамические области видимости

Использование локальных переменных — имеющих ограниченную область видимости и существующих лишь внутри текущей функции — помогает избежать конфликта имен между двумя переменными с одинаковыми именами. Однако существует два очень разных подхода к вопросу о том, что значит «быть внутри» функции и, соответственно, два варианта реализации локальной области видимости:

  • лексическая область видимости, или лексический контекст (англ. lexical scope), или лексическое (статическое) связывание (англ. lexical (static) binding): локальная область видимости функции ограничена текстом определения этой функции (имя переменной имеет значение внутри тела функции и считается неопределенным за его пределами).
  • динамическая область видимости, или динамический контекст (англ. dynamic scope), или динамическое связывание (англ. dynamic binding): локальная область видимости ограничена временем исполнения функции (имя доступно, пока функция выполняется, и исчезает, когда функция возвращает управление вызвавшему ее коду).

Для «чистых» функций, которые оперируют только своими параметрами и локальными переменными, лексическая и динамическая области видимости всегда совпадают. Проблемы возникают, когда функция использует внешние имена, например, глобальные переменные или локальные переменные функций, в которые она входит или из которых вызывается. Так, если функция 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

Легко видеть, что разница заключается в том, какое значение выводится в строке, помеченной комментарием со знаком вопроса.

  • В JavaScript областью видимости локальной переменной является вся функция, в том числе та ее часть, которая находится до объявления; при этом инициализация этой переменной выполняется только в момент обработки строки, где она находится. На момент первого вызова alert(scope) локальная переменная scope уже существует и доступна, но еще не получила значения, то есть, по правилам языка, имеет специальное значение undefined. Именно поэтому в помеченной строке будет выведено «undefined».
  • В Go используется более традиционный для этого типа языков подход, согласно которому область видимости имени начинается со строки, где оно объявляется. Поэтому внутри функции f(), но до объявления локальной переменной scope эта переменная недоступна, и помеченная знаком вопроса команда выводит значение глобальной переменной scope, то есть «global».

Блочная видимость

Еще один нюанс в семантике лексической области видимости — наличие или отсутствие так называемой «блочной видимости», то есть возможности объявить локальную переменную не только внутри функции, процедуры или модуля, но и внутри отдельного блока команд (в Си-подобных языках — заключенного в фигурные скобки {}). Далее приведен пример идентичного кода на двух языках, дающего разные результаты выполнения функции 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(), помеченным знаком вопроса в комментарии.

  • В JavaScript нет блочной области видимости, а повторное объявление локальной переменной работает просто как обычное присваивание. Присваивание x значений i внутри цикла for изменяет единственную локальную переменную x, которая была объявлена в начале функции. Поэтому после завершения цикла переменная x сохраняет последнее значение, присвоенное ей в цикле. Это значение и выводится в результате.
  • В Go блок операторов образует локальную область видимости, и объявляемая внутри цикла переменная x — это новая переменная, областью видимости которой является только тело цикла; она перекрывает x, объявленную в начале функции. Эта «дважды локальная» переменная получает в каждом проходе цикла новое значение и выводится, но ее изменения не затрагивают объявленную вне цикла переменную x. После завершения цикла объявленная в нем переменная х прекращает свое существование, а первая x становится снова видна. Ее значение остается прежним, оно и выводится в результате.

Видимость и существование объектов

Не следует отождествлять видимость идентификатора с существованием значения, с которым данный идентификатор связан. На соотношение видимости имени и существования объекта влияет логика программы и класс памяти объекта. Далее несколько типичных примеров.

  • Для переменных, память под которые выделяется и освобождается динамически (в куче), возможно любое соотношение видимости и существования. Переменная может быть объявлена и затем инициализирована, тогда объект, соответствующий имени, фактически появится позже вхождения в область видимости. Но объект может быть создан заранее, сохранен и затем присвоен переменной, то есть появиться раньше. То же и с удалением: после вызова команды удаления для переменной, связанной с динамическим объектом, сама переменная остается видимой, но ее значение не существует, а обращение к нему приведет к непредсказуемым результатам. С другой стороны, если команда удаления не вызвана, то объект в динамической памяти может продолжать существовать и после того, как ссылающаяся на него переменная вышла из области видимости.
  • Для локальных переменных со статическим классом памяти (в языках Си и C++) значение появляется (логически) в момент запуска программы. При этом имя находится в области видимости только при исполнении содержащей его функции. Причем в промежутках между функциями значение сохраняется.
  • Автоматические (в терминологии Си) переменные, создаваемые при входе в функцию и уничтожаемые при выходе, существуют в период времени, когда их имя видно. То есть для них времена доступности и существования практически можно считать совпадающими.

Примеры

Си

// Начинается глобальная область видимости.
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-функции
А дальше пошло хуже, но хоть как-то развивалось:

  • в Delphi «протекали» private-поля внутри модуля, потом протечки залепили с помощью strict protected;
  • в Java protected-поля протекают внутри пакета,
  • но потом появился ее близнец C#, в котором protected не протекает, а для контролируемых протечек внутри сборки придумали internal.


таким оразом, когда private var виден наружу, это именно «протечка», лучше называть вещи своими именами.
вероятно для этого, придумали, let и const .

Вау!! 😲 Ты еще не читал? Это зря!

Исследование, описанное в статье про область видимости, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое область видимости и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Информатика

создано: 2020-08-14
обновлено: 2021-12-19
132265



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:
Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Информатика

Термины: Информатика