Область видимості (програмування)У комп'ютерному програмуванні область дії зв'язування імені (англ. scope) — зв'язок імені з об'єктом (наприклад, змінною) — це частина програми, де зв'язування імені є дійсним, тобто ім'я може використовуватися для посилання на сутність. В інших частинах програми назва може посилатися на іншу сутність (вона може мати іншу прив'язку) або взагалі ні на що (вона може бути незв'язаною). Область допомагає запобігти зіткненням імен; це дозволяє одній і тій самій назві посилатися на різні об'єкти – якщо імена мають різні області. Область прив'язки імені також відомий як видимість сутності, особливо в старішій або більш технічній літературі — з точки зору сутності, на яку посилається, а не імені, на яке посилається. Термін «область» також використовується для позначення набору всіх зв'язків імен, дійсних у частині програми або в певній точці програми, що правильніше називати контекстом або середовищем.[a] У більшості мов програмування межі дії змінної зумовлені місцем її оголошення. Крім того, межі дії можна зазначити явно за допомогою класів пам'яті або просторів імен. Для більшості мов програмування[b], «частина програми» відноситься до частини вихідного коду (області тексту) і відома як лексична область видимості. Проте в деяких мовах «частина програми» відноситься до частини часу виконання (проміжку часу під час виконання) і відома як динамічна область. Обидва ці терміни є певною мірою оманливими — вони неправильно використовують технічні терміни, як обговорювалося у визначенні, — але саме розрізнення є точним, і це стандартні відповідні терміни. Лексичний обсяг є основним предметом цієї статті, при цьому динамічний обсяг розуміється на відміну від лексичного обсягу. Здебільшого розпізнавання імен на основі лексичного обсягу є відносно простим у використанні та реалізації, оскільки під час використання можна читати вихідний код у зворотному напрямку, щоб визначити, до якої сутності відноситься ім'я, а в реалізації можна підтримувати список імен і контексти під час компіляції або інтерпретації програми. Труднощі виникають у маскуванні імен, прямих деклараціях і підйомі, тоді як значно тонші виникають із нелокальними змінними, особливо в замиканнях. ВизначенняСуворе визначення (лексичного) «області» імені (ідентифікатора) є однозначним: лексичний обсяг — це «частина вихідного коду, в якій застосовується зв'язування імені з сутністю». Це практично не змінилося в порівнянні з визначенням 1960 року в специфікації ALGOL 60. Типові специфікації мови:
Найчастіше «область» означає, коли дане ім'я може посилатися на задану змінну — коли оголошення має ефект — але також може застосовуватися до інших сутностей, таких як функції, типи, класи, мітки, константи та переліки. Лексичний обсяг проти динамічногоФундаментальна відмінність у сфері застосування полягає в тому, що означає «частина програми». У мовах із лексичною областю видимості (також званою статичною областю видимості) дозвіл імен залежить від розташування у вихідному коді та лексичного контексту (також називається статичним контекстом), який визначається місцем визначення названої змінної або функції. У мовах із динамічною областю розпізнавання імен залежить від стану програми, коли зустрічається ім'я, що визначається контекстом виконання (також званим контекстом виклику або динамічним контекстом). На практиці з лексичним обсягом ім'я розпізнається шляхом пошуку в локальному лексичному контексті, а якщо це не вдається, шляхом пошуку в зовнішньому лексичному контексті тощо; тоді як з динамічною областю ім'я розпізнається шляхом пошуку в локальному контексті виконання, а якщо це не вдається, шляхом пошуку в зовнішньому контексті виконання тощо, просуваючись вгору по стеку викликів.[4] Більшість сучасних мов використовують лексичну область для змінних і функцій, хоча динамічна область використовується в деяких мовах, зокрема в деяких діалектах Lisp, деяких мовах «сценаріїв» і деяких мовах шаблонів.[c] Perl 5 пропонує як лексичну, так і динамічну область видимості. Навіть у мовах із лексичною областю видимість для замикань може збентежити, оскільки вони залежать від лексичного контексту, де визначено закриття, а не від того, де воно викликається. Лексичну роздільну здатність можна визначити під час компіляції, вона також відома як раннє зв'язування, тоді як динамічна роздільна здатність, як правило, може бути визначена лише під час виконання, і тому відома як пізнє зв'язування . Споріднені поняттяВ об'єктно-орієнтованому програмуванні динамічна розсилка вибирає метод об'єкта під час виконання, хоча те, чи виконується фактичне зв'язування імені під час компіляції чи під час виконання, залежить від мови. Фактична динамічна область видимості поширена в мовах макросів, які безпосередньо не розпізнають імена, а натомість розширюються на місці. Деякі фреймворки програмування, такі як AngularJS, використовують термін «область», щоб означати щось зовсім інше, ніж зазначене в цій статті. У цих фреймворках область видимості — це лише об'єкт мови програмування, яку вони використовують (JavaScript у випадку AngularJS), який певним чином використовується фреймворком для емуляції динамічної області видимості в мові, яка використовує лексичну область видимості для своїх змінних. Ці області AngularJS можуть самі бути в контексті або не в контексті (використовуючи звичайне значення терміну) у будь-якій частині програми, дотримуючись звичайних правил змінної області видимості мови, як і будь-якого іншого об'єкта, і використовуючи власне успадкування та правила включення. У контексті AngularJS іноді використовується термін «$scope» (зі знаком долара), щоб уникнути плутанини, але використання знака долара в іменах змінних часто не рекомендовано посібниками зі стилю.[5] Різновиди змінних залежно від меж діїУ мовах, що підтримують структурне програмування, змінні зазвичай поділяються на два типи залежно від видимості:
Об'єктно-орієнтоване програмування передбачає існування всередині кожного класу трьох нарізних ділянок з особливими межами дії:
ВикористанняОбласть є важливим компонентом розпізнавання імен[d], який, у свою чергу, є фундаментальним для семантики мови. Роздільна здатність імен (включаючи область дії) різниться між мовами програмування, а в межах мови програмування залежить від типу сутності; правила для області називаються правилами області (або правилами визначення). Разом із просторами імен правила області видимості є вирішальними в модульному програмуванні, тому зміна в одній частині програми не порушує непов'язану частину. ОглядГоворячи про обсяг, існує три основні поняття: обсяг, обсяг і контекст. Область — це властивість зв'язування імені, тоді як контекст — це властивість частини програми, яка є або частиною вихідного коду (лексичним контекстом чи статичним контекстом), або частина часу виконання (контекст виконання, контекст виклику або динамічний контекст). Контекст виконання складається з лексичного контексту (у поточній точці виконання) плюс додаткового стану виконання, такого як стек викликів.[e] Строго кажучи, під час виконання програма входить і виходить із різних областей прив'язки імен, і в момент виконання прив'язки імен знаходяться «в контексті» або «не в контексті», отже, прив'язки імен «входять у контекст» або «виходити з контексту», коли виконання програми входить або виходить з області видимості.[f] Однак на практиці використання набагато вільніше. Область — це концепція рівня вихідного коду та властивість зв'язків імен, зокрема зв'язків імен змінних або функцій — імена у вихідному коді є посиланнями на сутності в програмі — і є частиною поведінки компілятора чи інтерпретатора мови. Таким чином, питання обсягу подібні до вказівників, які є типом посилань, що використовуються в програмах більш загально. Використання значення змінної, коли ім'я знаходиться в контексті, але змінна неініціалізована, аналогічно розіменуванню (доступу до значення) завислого вказівника, оскільки він невизначений. Однак, оскільки змінні не знищуються, доки вони не вийдуть з контексту, аналога завислого вказівника не існує. Для таких сутностей, як змінні, область дії — це підмножина часу життя об'єкта (також відома як extent) — ім'я може посилатися лише на змінну, яка існує (можливо, з невизначеним значенням), але існуючі змінні не обов'язково є видимими: змінна може існувати, але бути недоступним (значення зберігається, але не посилається в даному контексті) або доступним, але не через задане ім'я, у цьому випадку воно не в контексті (програма «поза межами імені»). В інших випадках «тривалість життя» не має значення — мітка (іменована позиція у вихідному коді) має тривалість життя, ідентичну програмі (для статично скомпільованих мов), але може бути в контексті чи ні в певній точці програми, а також для статичні змінні — статична глобальна змінна знаходиться в контексті для всієї програми, тоді як статична локальна змінна знаходиться лише в контексті всередині функції або іншого локального контексту, але обидва мають час життя протягом усього виконання програми. Визначення того, на яку сутність посилається ім'я, відоме як розділення імен або зв'язування імен (зокрема в об'єктно-орієнтованому програмуванні) і залежить від мови. Отримавши назву, мова (власне, компілятор або інтерпретатор) перевіряє всі сутності, які знаходяться в контексті, на відповідність; у разі неоднозначності (дві сутності з однаковою назвою, наприклад глобальна та локальна змінна з однаковою назвою), для їх розрізнення використовуються правила розпізнавання імен. Найчастіше розпізнавання імен спирається на правило «внутрішнього контексту до зовнішнього», наприклад, правило Python LEGB (локальне, охоплююче, глобальне, вбудоване): імена неявно розв'язуються у найвужчому релевантному контексті. У деяких випадках розпізнавання імен може бути явно визначено, наприклад, за допомогою Коли дві ідентичні назви знаходяться в контексті одночасно, посилаючись на різні сутності, одне говорить про те, що відбувається маскування імен, коли ім'я з вищим пріоритетом (зазвичай найвнутрішнє) «маскує» ім'я з нижчим пріоритетом. На рівні змінних це відомо як затінення змінних . Через можливість логічних помилок через маскування деякі мови забороняють або не рекомендують маскувати, викликаючи помилку або попередження під час компіляції чи виконання. Різні мови програмування мають різні правила області видимості для різних типів оголошень і імен. Такі правила області видимості мають великий вплив на семантику мови і, як наслідок, на поведінку та коректність програм. У таких мовах, як C++, доступ до незв'язаної змінної не має чітко визначеної семантики та може призвести до невизначеної поведінки, подібної до посилання на завислий вказівник ; і оголошення або імена, що використовуються поза їхньою областю, створюватимуть синтаксичні помилки. Області часто прив'язані до інших мовних конструкцій і визначаються неявно, але багато мов також пропонують конструкції спеціально для керування областю. Рівні областіОбласть може варіюватися від лише одного виразу до всієї програми з багатьма можливими градаціями між ними. Найпростішим правилом видимості є глобальна область видимості — усі сутності видимі в усій програмі. Найпростішим правилом модульної області є дворівнева область, з глобальною областю в будь-якому місці програми та локальною областю в межах функції. Більш складне модульне програмування допускає окрему область модуля, де імена видимі всередині модуля (приватні для модуля), але не видимі за його межами. У межах функції деякі мови, такі як C, дозволяють обмежувати область дії блоку підмножиною функції; інші, особливо функціональні мови, дозволяють обмежувати область виразу одним виразом. Інші області включають область видимості файлу (зокрема в C), яка поводиться подібно до області видимості модуля, і блочну область поза функціями (особливо в Perl). Проблема полягає в тому, коли саме починається і закінчується область. У деяких мовах, таких як C, область дії імені починається з оголошення імені, і тому різні імена, оголошені в одному блоці, можуть мати різні області видимості. Це вимагає оголошення функцій перед використанням, хоча і не обов'язково їх визначення, і вимагає попереднього оголошення в деяких випадках, зокрема для взаємної рекурсії. В інших мовах, таких як Python, область імені починається на початку відповідного блоку, де ім'я оголошено (наприклад, початок функції), незалежно від того, де воно визначено, тому всі імена в межах даного блоку мають однаковий обсяг. У JavaScript область дії імені, оголошеного за допомогою Область виразуОбсяг прив'язки імені — це вираз, який відомий як область виразу. Область виразу доступна в багатьох мовах, особливо у функціональних мовах, які пропонують функцію під назвою let-вирази, що дозволяє області оголошення бути одним виразом. Це зручно, якщо, наприклад, для обчислення потрібне проміжне значення. Наприклад, у Standard ML, якщо У Python допоміжні змінні у виразах генератора та розуміння списків (у Python 3) мають область виразу. У C імена змінних у прототипі функції мають область виразу, відому в цьому контексті як область протоколу функції. Оскільки назви змінних у прототипі не згадуються (вони можуть відрізнятися у фактичному визначенні) — вони є просто фіктивними — їх часто опускають, хоча вони можуть використовуватися, наприклад, для створення документації. Область блокуОбсяг прив'язки імені — це блок, який відомий як область блоку. Блокова область доступна в багатьох, але не у всіх мовах програмування з блочною структурою. Це почалося з ALGOL 60, де «[кожна] декларація... дійсна лише для цього блоку», [8] і сьогодні це особливо пов'язано з мовами сімейств і традицій Pascal і C. Найчастіше цей блок міститься у функції, таким чином обмежуючи область видимості частиною функції, але в деяких випадках, наприклад у Perl, блок може бути не всередині функції. unsigned int sum_of_squares(const unsigned int N) {
unsigned int ret = 0;
for (unsigned int n = 1; n <= N; n++) {
const unsigned int n_squared = n * n;
ret += n_squared;
}
return ret;
}
Яскравим прикладом використання області видимості блоку є наведений тут код C, де дві змінні обмежені циклом: змінна циклу n, яка ініціалізується один раз і збільшується на кожній ітерації циклу, і допоміжна змінна n_squared, яка ініціалізується на кожній ітерації. Мета полягає в тому, щоб уникнути додавання змінних до області видимості функції, які мають відношення лише до певного блоку, наприклад, це запобігає помилкам, коли загальна змінна циклу i вже була випадково встановлена на інше значення. У цьому прикладі вираз Блоки в основному використовуються для потоку керування, наприклад із циклами if, while і for, і в цих випадках область дії блоку означає, що область дії змінної залежить від структури потоку виконання функції. Однак мови з блочною областю зазвичай також дозволяють використовувати «голі» блоки, єдиною метою яких є можливість детального керування змінною областю. Наприклад, допоміжна змінна може бути визначена в блоці, потім використана (скажімо, додана до змінної з областю дії функції) і відкинута, коли блок закінчується, або цикл while може бути укладений у блок, який ініціалізує змінні, що використовуються всередині циклу який слід ініціалізувати лише один раз. Тонкість кількох мов програмування, таких як Algol 68 і C (продемонстрована в цьому прикладі та стандартизована з C99 ), полягає в тому, що змінні області видимості блоку можуть бути оголошені не лише в тілі блоку, але також і в операторі керування, якщо будь-який. Це аналогічно параметрам функції, які оголошуються в декларації функції (перед початком блоку тіла функції) і в області видимості для всього тіла функції. Це в основному використовується в циклах for, які мають оператор ініціалізації, окремий від умови циклу, на відміну від циклів while, і є загальною ідіомою. Область блоку можна використовувати для затінення. У цьому прикладі всередині блоку допоміжну змінну також можна було б назвати n, затіняючи ім'я параметра, але це вважається поганим стилем через можливість помилок. Крім того, деякі нащадки C, такі як Java і C#, незважаючи на підтримку області видимості блоку (тобто локальну змінну можна вивести з контексту до завершення функції), не дозволяють одній локальній змінній приховувати іншу . У таких мовах спроба оголошення другого n призвела б до синтаксичної помилки, і одну з n змінних потрібно було б перейменувати. Якщо блок використовується для встановлення значення змінної, область дії блоку вимагає, щоб змінна була оголошена поза блоком. Це ускладнює використання умовних операторів з одним призначенням . Наприклад, у Python, який не використовує блокову область, можна ініціалізувати змінну як таку: if c:
a = "foo"
else:
a = ""
де У Perl, який має блочну область, замість цього потрібно оголосити змінну перед блоком: my $a;
if (c) {
$a = 'foo';
} else {
$a = '';
}
Часто замість цього це переписується за допомогою множинного призначення, ініціалізуючи змінну значенням за замовчуванням. У Python (де це не потрібно) це буде: a = ""
if c:
a = "foo"
тоді як у Perl це буде: my $a = '';
if (c) {
$a = 'foo';
}
У випадку призначення однієї змінної альтернативою є використання тернарного оператора, щоб уникнути блокування, але це, як правило, неможливо для призначення кількох змінних, і його важко прочитати для складної логіки. Це більш суттєва проблема в C, особливо для призначення рядка, оскільки ініціалізація рядка може автоматично виділяти пам'ять, тоді як призначення рядка вже ініціалізованій змінній вимагає виділення пам'яті, копії рядка та перевірки їх успішності. {
my $counter = 0;
sub increment_counter {
return ++$counter;
}
}
Деякі мови дозволяють застосовувати концепцію області видимості блоку, в різному ступені, поза функцією. Наприклад, у фрагменті Perl праворуч Область функціїКоли область змінних, оголошених у функції, не виходить за межі цієї функції, це називається областю видимості функції.[9] Область видимості функції доступна в більшості мов програмування, які пропонують спосіб створення локальної змінної у функції або підпрограми : змінної, область дії якої закінчується (що виходить з контексту), коли функція повертається. У більшості випадків час життя змінної дорівнює тривалості виклику функції — це автоматична змінна, створена під час запуску функції (або змінна оголошена), знищена, коли функція повертається — тоді як область видимості змінної знаходиться в межах функція, хоча значення "всередині" залежить від того, чи є область лексичною чи динамічною. Однак деякі мови, такі як C, також передбачають статичні локальні змінні, де час життя змінної дорівнює всьому часу життя програми, але змінна знаходиться в контексті лише всередині функції. У випадку статичних локальних змінних змінна створюється під час ініціалізації програми та знищується лише тоді, коли програма завершується, як у випадку зі статичною глобальною змінною, але знаходиться лише в контексті функції, як автоматична локальна змінна. Важливо, що в лексичній області видимості змінна з областю видимості функції має область видимості лише в межах лексичного контексту функції: вона виходить із контексту, коли інша функція викликається в межах функції, і повертається в контекст, коли функція повертається — викликані функції не мають доступу до локальних змінних функцій, що викликають, і локальні змінні знаходяться лише в контексті всередині тіла функції, в якій вони оголошені. Навпаки, у динамічній області видимості область поширюється на контекст виконання функції: локальні змінні залишаються в контексті, коли викликається інша функція, виходячи з контексту лише після завершення функції, що визначає, і, таким чином, локальні змінні знаходяться в контексті функції в якому вони визначені та всі називаються функціями . У мовах з лексичним обсягом і вкладеними функціями локальні змінні знаходяться в контексті для вкладених функцій, оскільки вони знаходяться в тому самому лексичному контексті, але не для інших функцій, які не є лексично вкладеними. Локальна змінна охоплюючої функції відома як нелокальна змінна для вкладеної функції. Область дії функції також застосовна до анонімних функцій. def square(n):
return n * n
def sum_of_squares(n):
total = 0
i = 0
while i <= n:
total += square(i)
i += 1
return total
Наприклад, у фрагменті коду Python праворуч визначено дві функції: Кожна з цих функцій має змінну з іменем n, яка представляє аргумент функції. Ці дві n змінних повністю окремі та не пов'язані, незважаючи на те саме ім'я, тому що вони є локальними змінними з лексичною областю видимості з областю видимості функції: область видимості кожної з них є окремою лексично окремою функцією, і тому вони не збігаються. Таким чином, Ніякого маскування імен не відбувається: лише одна змінна з іменем n знаходиться в контексті в будь-який момент часу, оскільки області не перекриваються. Навпаки, якби подібний фрагмент був написаний мовою з динамічною областю видимості, n у функції, що викликає, залишався б у контексті викликаної функції — області видимості перекривалися б — і були б замасковані («затінені») новим n у викликаній функції. Область дії функції значно ускладнюється, якщо функції є об'єктами першого класу і можуть бути створені локально для функції, а потім повернуті. У цьому випадку будь-які змінні у вкладеній функції, які не є локальними для неї (незв'язані змінні у визначенні функції, які перетворюються на змінні в охоплюючому контексті), створюють закриття, оскільки не лише сама функція, але й її контекст (змінних ) потрібно повернути, а потім потенційно викликати в іншому контексті. Це вимагає значно більшої підтримки від компілятора та може ускладнити аналіз програми. Мови розміткиПоняття меж дії також стосується мов розмітки. Наприклад, в HTML межі дії імені поля введення (те саме стосується прапорців, ґудзиків тощо) збігаються з межами форми (HTML) від <form> до </form>[10]. ПрикладиCПриклад областей видимості змінних у мові програмування C: // Змінна foo має глобальні межі дії
int foo = 0;
int main() {
// Межами дії змінної bar є тіло функції main
int bar = 1;
}
# include <stdio.h>
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
}
}
}
Примітки
Коментарі
|