Оператор присвоювання

Присвоєння (англ. assignment) — механізм в програмуванні, що дозволяє динамічно змінювати зв'язки об'єктів даних (зазвичай, змінних) з їхніми значеннями. Строго кажучи, зміна значень є побічним ефектом операції присвоєння, і в багатьох сучасних мовах програмування сама операція також повертає певний результат (як правило, копію присвоєного значення). На фізичному рівні результат операції присвоєння полягає в проведенні запису і перезапису фрагментів пам'яті або регістрів процесора.

Присвоєння — одна з центральних конструкцій в імперативних мовах програмування, ефективно і просто реалізується на фон-Нейманівській архітектурі, яка лежить в основі сучасних комп'ютерів.

В логічному програмуванні прийнято інший, алгебраїчний підхід. Звичайного («деструктивного») присвоєння тут немає. Існують тільки невідомі, які ще не обчислені, і відповідні ідентифікатори для позначення цих невідомих. Програма тільки визначає їхні значення, самі вони сталі. Звичайно, в реалізації програма робить запис в пам'ять, але мови програмування цього не відображають, даючи програмісту можливість працювати з ідентифікаторами постійних значень, а не зі змінними.

В чистому функційному програмуванні не використовують змінні, тому явний оператор присвоєння не потрібен.

Визначення присвоєння

Загальний синтаксис простого присвоєння такий:

<вираз зліва> <оператор присвоєння> <вираз справа>

Результатом обчислення «Вираз зліва» має бути місце розташування об'єкта даних, або цільова змінна, ідентифікатор комірки пам'яті, куди буде здійснено запис. Такі посилання називаються «лівостороннім значеннями» (англ. lvalue). Типові приклади лівостороннього значення:

  • ім'я змінної x
  • шлях до змінної в просторі імен і бібліотеках Namespace.Library.Object.AnotherObject.Property
  • шлях до масиву з виразом на місці індексу this.a[i+j*k].

Нижче буде наведено складніші приклади.

«Вираз справа» має позначати в той чи інший спосіб величину, яку буде присвоєно об'єктові даних. Таким чином, навіть якщо в правій частині ім'я тієї ж змінної, що і ліворуч, інтерпретується воно інакше — такі посилання називаються «правостороннє значення» (англ. rvalue). Подальші обмеження на вираз накладає використана мова: так, в статично типізованих мовах воно повинно мати той же тип, що і цільова змінна, або тип, що можна привести до нього; в деяких мовах (наприклад, Сі або Python) у вираз може входити також інша операція присвоєння a=b=c.

Оператором присвоєння в мовах програмування найчастіше виступають =, := або Але спеціальний синтаксис може і не вводиться — наприклад, в Tcl:

set <цільова_змінна> <вираз>

Цей запис еквівалентний виклику функції. Аналогічно, в Коболі старого стилю:

MULTIPLY 2 BY 2 GIVING FOUR.

Алгоритм роботи оператора присвоєння

Обчислити значення першого операнда (ліва частина)
На цьому етапі стає відомим місце знаходження цільового об'єкта, приймача нового значення.
Обчислити значення другого операнда (права частина)
Цей етап може бути як завгодно великим і включати інші оператори (у тому числі присвоєння).
Присвоїти обчислене значення другого операнда першому
По-перше, при конфлікті типів має бути здійснено їхнє приведення (або видане повідомлення про помилку зважаючи на його неможливості). По-друге, власне присвоювання значення в сучасних мовах програмування може бути підмінене і включати не тільки перенесення значень комірок пам'яті (наприклад, «властивості» об'єктів в C #, перевантаження операторів).
Повернути значення другого операнда як результат виконання операції
присутній не у всіх мовах (наприклад, не присутній в Паскалі).

Символ присвоєння

Вибір символу присвоєння викликає суперечки серед розробників мов. Існує думка, що використання символу = для присвоєння заплутує програмістів, а також ставить складне питання про вибір символу для оператора порівняння.

Так, Ніклаус Вірт стверджував[1] :

Ця погана ідея відкидає вікову традицію використання знаку «=» для позначення порівняння на рівність, предиката, що приймає значення «істина» або «невірно».

Вибір символу оператора рівності в мові при використанні = як присвоєння вирішується:

  • Введенням нового символу мови для оператора перевірки на рівність.

Запис рівності у мові C == є джерелом помилок через можливість використання присвоєння в керівних конструкціях, але в інших мовах проблема вирішується введенням додаткових обмежень.

  • Використанням цього ж символу, призначення визначають виходячи з контексту.

Наприклад, у виразі мовою PL/I:

А = В = С

змінній А присвоюється логічне значення виразу В = С Такий запис погіршує сприйняття і рідко використовується.

Семантичні особливості

В імперативних мовах не завжди спосіб інтерпретації присвоєння «інтуїтивний», єдино вірний і можливий.

В імперативних мовах іноді неможливо зрозуміти за використовуваним синтаксисом як реалізується семантика присвоєння, якщо це явно не визначено в мові.

Наприклад, у мові Forth використовується присвоювання значення, коли дані між операціями, проходять через стек даних, при цьому сама операція не є покажчиком на дані, а лише виконує дії визначені операцією.

Унаслідок можна зробити присвоювання даних, сформованих (розташованих) далеко від операції присвоєння.

Приклад для сказаного вище:

\ Визначення змінної AAA і наступним рядком присвоєння їй значення 10
VARIABLE AAAA
10 AAA!

або так те ж саме (семантично):

10
VARIABLE AAA
AAA!

Неоднозначність присвоєння

Розглянемо приклад:

X = 2 + 1

Це можна зрозуміти як «результат обчислення 2+1 (тобто 3) присвоюється змінній X», або як «операція 2 + 1 присвоюється змінній X». Якщо мова статично типізована, то двозначності немає, результат визначається типом змінної X («ціле число» або «операція»). У мові Пролог типізація динамічна, тому існують дві операції присвоєння: is — присвоєння еквівалентного значення і = — присвоєння зразка. У такому разі:

X is 2 + 1, X = 3
X = 2 + 1, X = 3

Перша послідовність буде визнана істинною, друга — хибною.

Семантика посилань

При роботі з об'єктами великих розмірів і складної структури багато мов використовують так звану «семантику посилань». Це означає, що присвоєння в класичному розумінні не відбувається, але вважається, що значення цільової змінної розташовується на тому ж місці, що і значення вихідної. Наприклад (Python):

a = [1, 2, 3]
b = a
a [1] = 1000

Після цього b матиме значення [1, 1000, 3] — просто тому, що фактично його значення — це і є значення a. Кількість посилань на один і той самий об'єкт даних називається його потужністю, а сам об'єкт зникає (знищується або усувається прибиральником сміття), коли його потужність досягає нуля. Мови програмування нижчого рівня (наприклад, Сі) дозволяють програмісту явно управляти тим, чи використовується семантика посилань або семантика копіювання.

Підміна операції

Багато мов надають можливість змінювати сенс присвоєння: або через механізм властивостей, або через перевантаження оператора присвоєння. Підміна може знадобиться для виконання перевірок на допустимість присвоюється значення або будь-яких інших додаткових операцій. Перевантаження оператора присвоєння часто використовується для забезпечення «глибокого копіювання», тобто копіювання значень, а не посилань, які в багатьох мовах копіюються за замовчуванням.

Такі механізми дозволяють забезпечити зручність при роботі, оскільки для програміста нема різниці між використанням вбудованого оператора і перевантаженого. З цієї ж причини можливі проблеми, тому що дії перевантаженого оператора можуть бути абсолютно відмінні від дій оператора за замовченням, а виклик функції не очевидний і легко може бути прийнятий за вбудовану операцію.

Розширення конструкції присвоєння

Оскільки операція присвоєння широко використовується, розробники мов програмування намагаються розробити нові конструкції для спрощеного запису типових операцій (додати в мову так званий «синтаксичний цукор»). Крім цього, в низькорівневих мовах програмування часто критерієм включення операції є можливість компіляції в ефективний виконуваний код.[2] Особливо відома цією властивістю мова Сі.

Множинні цільові об'єкти

Однією з альтернатив простого оператора є можливість присвоєння значення виразу декільком об'єктам. Наприклад, у мові PL/I оператор

SUM, TOTAL = 0

одночасно присвоює нульове значення змінним SUM і TOTAL. В мові Ada присвоєння також є оператором, а не виразом, тому запис множинного присвоєння має вигляд:

SUM, TOTAL: Integer: = 0;

Аналогічне присвоєння в мові Python має такий синтаксис:

sum = total = 0

На відміну від PL/I, Ada та Python, де множинне присвоєння вважається тільки скороченою формою запису, в мовах C, Lisp та інших цей синтаксис має строгу основу: просто оператор присвоєння повертає присвоєне ним значення (див. вище). Таким чином, останній приклад — це насправді:

sum = (total = 0)

Рядок такого виду спрацює в Сі (якщо додати крапку з комою в кінці), але викличе помилку в Python.

Паралельне присвоєння

Для деяких мов, наприклад Ruby та Python, підтримують розширений синтаксис присвоєння, який називається паралельним присвоєнням:

a, b = 1, 11

Вважається, що таке присвоєння виконується одночасно і паралельно, що дозволяє коротко реалізувати за допомогою такої конструкції операцію обміну значень двох змінних.

запис з використанням паралельного присвоєння «традиційне» присвоєння: вимагає додаткової змінної та трьох операцій «економне» присвоєння: не вимагає додаткової змінної, але також містить три операції
a, b = b, a
t = a
a = b
b = t
a = a + b
b = a - b
a = a - b

Деякі мови (наприклад, PHP) мають конструкції, що дозволяють зімітувати паралельне присвоювання:

list ($ a, $ b) = array ($ b, $ a);

Умовне присвоєння

Деякі мови програмування, наприклад, C++ і Java, дозволяють умовне присвоєння. Наприклад, вираз: var = flag ? count1 : count2; присвоїть змінній var значення count1, якщо flag має значення «true» і count2, якщо flag «false».

Інший варіант умовного присвоєння (Ruby):

a || = 10

Дана конструкція присвоює змінній a значення тільки в тому випадку, якщо значення ще не присвоєно або дорівнює false

Складені оператори присвоєння

Складений оператор присвоєння дозволяє скорочено задавати часто використовувану форму присвоєння. За допомогою цього способу можна скоротити запис присвоєння, при якому цільова змінна використовується як перший операнд в правій частині виразу, наприклад:

а = а + b

Синтаксис складного оператора присвоєння мови Сі є об'єднанням потрібного бінарного оператора і оператора = Наприклад, такі записи еквівалентні

sum + = value;

sum = sum + value;

У мовах програмування, що підтримують складені оператори (C++, C#, Python, Java та інші), звичайно існують версії для більшості бінарних операторів цих мов += -= &= і т. ін.).

Унарні оператори присвоєння

У мові програмування С і більшості похідних від неї присутні два спеціальних унарних (тобто мають один аргумент) арифметичних оператора, які насправді є скороченим присвоєнням. Ці оператори поєднують операції збільшення та зменшення на одиницю з присвоєнням. Оператори ++ для збільшення і -- для зменшення можуть використовуватися як префіксні оператори (тобто перед операндами) або як постфіксні (тобто після операндів), означаючи різний порядок обчислення. Префіксний оператор інкременту повертає вже збільшене значення операнда, а постфіксний — оригінал.

Нижче наведений приклад використання оператора інкременту для формування завершеного оператора присвоєння

збільшення значення змінної на одиницю еквівалентний розширений запис
count++; count = count + 1;

Хоч це і не виглядає присвоєнням, але є ним. Результат виконання наведеного вище оператора рівнозначний результату виконання присвоєння.

Оператори інкременту і декременту в мові Сі часто є скороченим записом для формування виразів, що містять індекси масивів.

Реалізація

Робота сучасних комп'ютерів складається зі зчитування даних з пам'яті в регістри, виконання операцій над цими даними і запису в пам'ять. Основною операцією тут є передача даних (з регістрів в пам'ять, з пам'яті в регістр, з регістра в регістр). Відповідно, вона виражається безпосередньо командами сучасних процесорів. Так, для архітектури x86 (усі наведені нижче команди відносяться також до даної архітектурі) це операція mov та її різновиди для передачі даних різних розмірів. Операція присвоєння (передача даних з однієї комірки пам'яті в іншу) практично безпосередньо реалізується цією командою. Взагалі кажучи, для виконання передачі даних в пам'яті потрібно дві інструкції: передача з пам'яті в регістр і з регістра в пам'ять, але при використанні оптимізації в більшості випадків кількість команд можна скоротити.

mov eax, [ebp-4]      # Завантажити 32-бітне значення з пам'яті
mov [ebp+8], eax      # Записати це значення за іншою адресою

Примітки

  1. Ніклаус Вірт. (2006). Хороші ідеї: погляд із Задзеркалля. Пер. Сергій Кузнецов. Процитовано 23 квітня 2006.
  2. З метою оптимізації багато операцій поєднуються в присвоювання. Для скорочених записів присвоєння найчастіше еквівалентне машинним інструкціях. Так, збільшення на одиницю реалізується машинної інструкцією inc зменшення на одиницю dec додавання з присвоєнням add віднімання з присвоєнням sub команди умовної пересилання cmova cmovno і т. д.

Див. також

Література

  • Роберт В. Себеста. Основные концепции языков программирования = Concepts of Programming Languages. — 5-е изд. — М. : «Вільямс», 2001. — С. 275-298. — ISBN 0-201-75295-6.
  • М. Бен-Ари. Языки программирования. Практический сравнительный анализ. — М.: Мир, 2000. — 366 с. С. 71—74.
  • В. Э. Вольфенгаген. Конструкции языков программирования. Приёмы описания. — М.: АО «Центр ЮрИнфоР», 2001. — 276 с. ISBN 5-89158-079-9. С. 128—131.
  • Э. А. Опалева, В. П. Самойленко. Языки программирования и методы трансляции. — СПб.: БХВ-Петербург, 2005. — 480 с. ISBN 5-94157-327-8. С. 74—75.
  • Т. Пратт, М. Зелковиц. Языки программирования: разработка и реализация. — 4-е изд. — СПб: Питер, 2002. — 688 с. ISBN 5-318-00189-0, ISBN 0-13-027678-2. С. 201—204.


This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.