Misiune

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 29 martie 2021; verificările necesită 23 de modificări .

Atribuirea  este un mecanism de legare în programare care vă permite să schimbați în mod dinamic relația dintre numele obiectelor de date (de obicei variabile ) cu valorile acestora. Strict vorbind, modificarea valorilor este un efect secundar al operațiunii de atribuire, iar în multe limbaje de programare moderne, operația în sine returnează și un rezultat (de obicei o copie a valorii atribuite). La nivel fizic, rezultatul unei operațiuni de atribuire este de a scrie și rescrie celulele de memorie sau registrele procesorului .

Assignment este unul dintre constructele centrale în limbaje de programare imperative , implementat eficient și simplu pe arhitectura von Neumann care stă la baza computerelor moderne .

În limbajele de programare orientate pe obiecte, semantica atribuirii este destul de diferită. De exemplu, în limbajul Kotlin , la atribuire, obiectul este copiat, iar în limbajul Rust , obiectul este mutat (move-semantics) și vechiul pachet devine invalid.

Programarea logică are o abordare diferită, algebrică . Nu există o misiune obișnuită („distructivă”) aici. Există doar necunoscute care nu au fost încă calculate și identificatori corespunzători pentru a denota acele necunoscute. Programul determină doar valorile lor, ele însele sunt constante. Desigur, în implementare, programul scrie în memorie, dar limbajele de programare nu reflectă acest lucru, oferind programatorului posibilitatea de a lucra cu identificatori de valori constante și nu cu variabile.

Programarea pură funcțională nu utilizează variabile și nu are nevoie de o declarație explicită de atribuire.

Definiție

Sintaxa generală pentru o atribuire simplă este următoarea:

<expresie din stânga> <operator de atribuire> <expresie din dreapta>

„Expresia din stânga” ar trebui, după evaluare, să conducă la locația obiectului de date, la variabila țintă, identificatorul celulei de memorie la care se va face înregistrarea. Astfel de referințe sunt numite „left-values” ( în engleză  lvalue ). Exemple tipice de valoare pentru stânga sunt un nume de variabilă ( x), o cale către o variabilă în spațiul de nume și biblioteci ( Namespace.Library.Object.AnotherObject.Property), o cale de matrice cu o expresie în locul indexului ( this.a[i+j*k]), dar opțiunile mai complexe sunt date mai târziu în acest articol. articol.

„Expresia din dreapta” trebuie să indice într-un fel sau altul valoarea de atribuit obiectului de date. Astfel, chiar dacă numele aceleiași variabile este în dreapta ca și în stânga, acesta este interpretat diferit - astfel de referințe se numesc „valori de la dreapta” ( în engleză  rvalue ). Limbajul folosit impune restricții suplimentare asupra expresiei : astfel, în limbile tipizate static , ea trebuie să aibă același tip ca variabila țintă, sau un tip turnat la aceasta; în unele limbi (de exemplu, C sau Pythona=b=c ), poate fi inclus și un alt operator de atribuire ( ) în expresie.

Cel mai comun operator de atribuire în limbajele de programare este =, :=sau ←. Dar este posibil ca sintaxa specială să nu fie introdusă - de exemplu, în Tcl :

setați <variabilă_țintă> <expresie>

Această notație este echivalentă cu apelarea unei funcții . În mod similar, în stilul vechi COBOL :

ÎN MULTEȘTE 2 CU 2 DĂ PATRU.

Algoritm de operare

Denumire

Alegerea simbolului misiunii este o chestiune de controversă în rândul designerilor de limbi. Există opinia că utilizarea unui simbol =pentru atribuire încurcă programatorii și ridică, de asemenea, problema alegerii unui simbol pentru operatorul de comparație , care este greu de rezolvat bine .

Astfel, Niklaus Wirth a afirmat [1] :

Un exemplu prost bine-cunoscut este alegerea unui semn egal pentru a desemna o misiune, care datează din Fortran în 1957 și este încă repetat orbește de o masă de dezvoltatori de limbi. Această idee proastă răstoarnă tradiția veche de a folosi semnul „ = ” pentru a denota o comparație de egalitate, un predicat care se evaluează ca „ adevărat ” sau „ fals ”. Dar în Fortran, acest simbol a început să denote atribuirea, constrângerea la egalitate. În acest caz, operanzii sunt într-o poziție inegală: operandul din stânga, variabila, trebuie făcută egală cu operandul din dreapta, expresia. Deci x = y nu înseamnă același lucru ca y = x.

Text original  (engleză)[ arataascunde] Un exemplu notoriu pentru o idee proastă a fost alegerea semnului egal pentru a indica atribuirea. Se întoarce la Fortran în 1957 și a fost copiat orbește de armatele de designeri lingvistici. De ce este o idee proastă? Pentru că răstoarnă o tradiție veche de un secol de a lăsa „=” să desemneze o comparație pentru egalitate, un predicat care este fie adevărat, fie fals. Dar Fortran a făcut-o să însemne însărcinare, impunerea egalității. În acest caz, operanzii sunt pe picior inegal: Operandul din stânga (o variabilă) trebuie să fie egal cu operandul din dreapta (o expresie). x = y nu înseamnă același lucru cu y = x. [2]

Implementarea acestei poziții a lui Wirth poate fi considerată că în limbajul Pascal , al cărui autor este el, operatorul de atribuire este :=, în timp ce pentru comparație este folosit simplu =.

Alegerea simbolului operatorului de egalitate în limbă atunci când este utilizat =ca sarcină este decisă de:

Notarea egalității în C == este o sursă de erori frecvente datorită posibilității de a folosi atribuirea în constructele de control, dar în alte limbi problema este rezolvată prin introducerea de restricții suplimentare.

De exemplu, în expresia limbajului PL/1 :

A = B = C

variabilei i se Аatribuie valoarea booleană a expresiei relaţiei В = С. O astfel de notație duce la o lizibilitate redusă și este rar folosită.

Caracteristici semantice

Departe de a fi întotdeauna „intuitiv” (pentru programatorii de limbaje imperative) modul de interpretare a sarcinii este singurul adevărat și posibil.

Din sintaxa folosită în limbajele imperative, nu este întotdeauna posibil să înțelegem cum este implementată semantica atribuirii decât dacă este definită în mod explicit în limbaj.

De exemplu, în Forth , înainte de atribuire, valoarea și adresa unei variabile trebuie să treacă în stiva de date, iar acest lucru se poate face cu mult înainte ca atribuirea efectivă să fie efectuată.

Exemplu:

\ Definirea variabilei AAA și atribuirea acesteia la valoarea 10 în rândul următor VARIABIL AAA 10 AAA!

Același lucru puțin diferit:

zece VARIABIL AAA AAA! Ambiguitate

Luați în considerare un exemplu:

X=2+1

Acest lucru poate fi înțeles ca „rezultatul calculului 2+1 (adică 3) este atribuit unei variabile X” sau ca „operația 2+1 este atribuită unei variabile X”. Dacă limbajul este tastat static , atunci nu există ambiguitate, se rezolvă prin tipul variabilei X("întreg" sau "operație"). În Prolog , tastarea este dinamică , deci există două operații de atribuire: is - atribuirea unei valori echivalente și = - atribuirea unui model. În acest caz:

X este 2 + 1, X = 3 X=2+1, X=3

Prima secvență va fi recunoscută ca adevărată, a doua - falsă.

Text

Atunci când se ocupă cu obiecte de dimensiuni mari și structură complexă, multe limbi folosesc așa-numita „ semantică de referință ”. Aceasta înseamnă că atribuirea în sensul clasic nu are loc, dar valoarea variabilei țintă este considerată a fi situată în același loc cu valoarea variabilei sursă. De exemplu ( Python ):

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

După aceea, bva avea o valoare [1, 1000, 3] - pur și simplu pentru că, de fapt, valoarea sa este valoarea lui a. Numărul de referințe la același obiect de date se numește cardinalitate, iar obiectul în sine este ucis (distrus sau dat colectorului de gunoi ) atunci când cardinalitatea lui ajunge la zero. Limbajele de programare de nivel inferior (cum ar fi C ) permit programatorului să controleze în mod explicit dacă se folosește semantica pointerului sau semantica copiei.

Operațiune de înlocuire

Multe limbi oferă posibilitatea de a schimba sensul unei sarcini, fie prin mecanismul proprietății , fie prin supraîncărcarea operatorului de atribuire. Înlocuirea poate fi necesară pentru a efectua verificări asupra validității valorii atribuite sau a oricăror alte operațiuni suplimentare. Supraîncărcarea operatorului de atribuire este adesea folosită pentru a oferi o „copie profundă”, adică copierea valorilor mai degrabă decât a referințelor, care sunt copiate implicit în multe limbi.

Astfel de mecanisme fac posibilă asigurarea confortului la locul de muncă, astfel încât pentru un programator nu există nicio diferență între utilizarea unui operator încorporat și a unuia supraîncărcat. Din același motiv, sunt posibile probleme, deoarece acțiunile operatorului supraîncărcat pot fi complet diferite de acțiunile operatorului implicit, iar apelul funcției nu este evident și poate fi ușor confundat cu o operație încorporată.

Modele extinse

Deoarece operatorul de atribuire este utilizat pe scară largă, dezvoltatorii de limbaje de programare încearcă să dezvolte noi constructe pentru a simplifica scrierea operațiilor tipice (pentru a adăuga așa-numitul „ zahăr sintactic ” în limbaj). În plus, în limbajele de programare de nivel scăzut, criteriul de includere este adesea capacitatea de a compila în cod executabil eficient. [3] Limbajul C este renumit în special pentru această proprietate .

Ținte multiple

O alternativă la operatorul simplu este capacitatea de a atribui valoarea unei expresii mai multor obiecte . De exemplu, în PL/1 , operatorul

SUMĂ, TOTAL = 0

atribuie simultan zero variabilelor SUMși TOTAL. În Ada , atribuirea este, de asemenea, o declarație, nu o expresie, deci notația pentru atribuirea multiplă este:

SUMA, TOTAL: Număr întreg := 0;

O atribuire similară în Python are următoarea sintaxă:

suma = total = 0

Spre deosebire de PL/1, Ada și Python, unde atribuirea multiplă este considerată doar o notație scurtă, în C , Lisp și altele, această sintaxă are o bază strictă: operatorul de atribuire returnează pur și simplu valoarea care i-a fost atribuită (vezi mai sus). Deci ultimul exemplu este de fapt:

suma = (total = 0)

O linie ca aceasta va funcționa în C (dacă adăugați un punct și virgulă la sfârșit), dar va provoca o eroare în Python.

Atribuire paralelă

Unele limbi, cum ar fi Ruby și Python , acceptă o sintaxă extinsă de atribuire numită atribuire paralelă:

a , b = 1 , 11

Se crede că o astfel de atribuire este efectuată simultan și în paralel , ceea ce face posibilă implementarea pe scurt, folosind această construcție, a operațiunii de schimb de valori a două variabile.

Scrierea folosind atribuirea paralelă Atribuire „tradițională”: necesită o variabilă suplimentară și trei operații Atribuire „economică”: nu necesită o variabilă suplimentară, dar conține și trei operații Atribuire și mai „economică”: nu necesită o variabilă suplimentară, funcționează cu operații pe biți
a, b = b, a t = a a = b b=t a = a + b b = a - b a = a - b a ^= b b ^= a a ^= b

Penultima opțiune de aritmetică este nesigură în limbajele de programare sau platformele hardware care verifică depășirile aritmetice .

Ultima opțiune funcționează doar cu tipuri care acceptă operații pe biți (de exemplu, doublecompilatorul C# nu vă va permite să schimbați valori variabile în acest fel).

Unele limbi (cum ar fi PHP ) au constructe pentru a simula atribuirea paralelă:

lista ( $a , $b ) = matrice ( $b , $a );

Ținte condiționate

Unele limbaje de programare, cum ar fi C++ , permit ținte condiționate în instrucțiunile de atribuire. De exemplu, expresia:

( flag ? count1 : count2 ) = 0 ;

va atribui o valoare 0variabilei count1dacă și dacă . flag==truecount2flag==false

O altă variantă de atribuire condiționată ( Ruby ):

a ||= 10

Acest construct atribuie o avaloare unei variabile numai dacă valoarea nu a fost încă atribuită sau este egală cu false.

Declarații compuse

Operatorul de atribuire compusă vă permite să prescurtați o formă de atribuire utilizată în mod obișnuit. Folosind această metodă, puteți scurta notația unei atribuiri care utilizează variabila țintă ca prim operand din partea dreaptă a expresiei, de exemplu:

a = a + b

Sintaxa operatorului de atribuire compus C este uniunea operatorului binar dorit și =. De exemplu, următoarele intrări sunt echivalente

sum += value; sum = sum + value;

Limbajele de programare care acceptă operatori compuși ( C++ , C# , Python , Java etc.) au de obicei versiuni pentru majoritatea operatorilor binari ai acelor limbaje ( +=, -=, &=etc.).

Operatori unari

În limbajele familiei C , există patru operatori aritmetici unari (adică luând un singur argument) pentru creșterea și decrementarea numerelor cu unul: doi operatori „ ” și doi operatori „”. Operatorii se pot scrie înaintea operandului (prefix) sau după acesta (postfix sau sufix). Operatorii de prefix și postfix diferă în ordinea evaluării. Operatorii de prefix modifică un număr câte unul și returnează numărul modificat. Operatorii Postfix stochează un număr într-o variabilă temporară, modifică numărul original și returnează valoarea variabilei temporare. ++--

Un exemplu de utilizare a operatorului : ++

Creșterea valorii unei variabile cu una Notație echivalentă
count ++; count = count + 1;

Deși nu pare o misiune, este. Rezultatul executării instrucțiunii de mai sus este același cu rezultatul executării sarcinii.

Operatorii " " se numesc operatori de creştere, iar operatorii " " se numesc operatori de decrementare. Operatorii sunt adesea folosiți în limbajul C atunci când se ocupă cu pointeri și indici de matrice . ++--

Implementare

Funcționarea computerelor moderne constă în citirea datelor din memorie sau dispozitiv în registre, efectuarea de operații pe acele date și scrierea în memorie sau dispozitiv. Operația principală aici este transferul de date (din registre în memorie, din memorie în registru, din registru în registru). În consecință, este exprimat direct de instrucțiunile procesoarelor moderne . Deci, pentru arhitectura x86 (toate comenzile de mai jos se aplică și acestei arhitecturi), aceasta este o operațiune movși varietățile sale pentru trimiterea de date de diferite dimensiuni. Operația de atribuire (transferul datelor de la o celulă de memorie la alta) este practic implementată direct de această comandă. În general, sunt necesare două instrucțiuni pentru a efectua un transfer de date în memorie: o mutare de la memorie la registru și una de la registru la memorie, dar cu optimizări, numărul de instrucțiuni poate fi redus în majoritatea cazurilor.

movl -4(%ebp), %eax
movl %eax, 8(%ebp)

Exemplu de generare de cod ( GCC ), două
instrucțiuni pentru atribuire

Vezi și

Note

  1. Niklaus Wirth . Idei bune: Privirea prin oglindă . Pe. Serghei Kuznetsov (2006). Consultat la 23 aprilie 2006. Arhivat din original pe 27 septembrie 2011.
  2. Niklaus Wirth. Idei bune, prin oglindă . Preluat la 4 decembrie 2010. Arhivat din original la 25 iunie 2012.
  3. În scopuri de optimizare, multe operații sunt combinate cu atribuirea. Misiunile de stenografie au adesea un echivalent în instrucțiunile mașinii. Deci, creșterea cu unu este implementată de instrucțiunea mașinii inc, scăderea cu unu - dec, adunarea cu atribuire - add, scăderea cu atribuire - sub, instrucțiunile de transfer condiționat - cmova, cmovnoetc.

Literatură

  • Robert W. Sebesta. Concepte de bază ale limbajelor de programare \u003d Concepte ale limbajelor de programare. - a 5-a ed. - M. : Williams , 2001. - 672 p. - ISBN 0-201-75295-6 .
  • M. Ben-Ari. Limbaje de programare. Analiză comparativă practică. — M.: Mir, 2000. — 366 p. pp. 71-74.
  • V. E. Wolfenhagen. Design de limbaje de programare. Metode de descriere. - M .: SA Centrul YurInfoR, 2001. - 276 p. ISBN 5-89158-079-9 . p. 128-131.
  • E. A. Opaleva, V. P. Samoilenko. Limbaje de programare și metode de traducere. - Sankt Petersburg: BHV-Petersburg, 2005. - 480 p. ISBN 5-94157-327-8 . pp. 74-75.
  • T. Pratt, M. Zelkowitz. Limbaje de programare: dezvoltare și implementare. - a 4-a ed. - Sankt Petersburg: Peter, 2002. - 688 p. ISBN 5-318-00189-0 , ISBN 0-13-027678-2 . pp. 201-204.