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.
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.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 = Cvariabilei i se Аatribuie valoarea booleană a expresiei relaţiei В = С. O astfel de notație duce la o lizibilitate redusă și este rar folosită.
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! AmbiguitateLuați în considerare un exemplu:
X=2+1Acest 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=3Prima secvență va fi recunoscută ca adevărată, a doua - falsă.
TextAtunci 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] = 1000După 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 înlocuireMulte 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ă.
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 .
O alternativă la operatorul simplu este capacitatea de a atribui valoarea unei expresii mai multor obiecte . De exemplu, în PL/1 , operatorul
SUMĂ, TOTAL = 0atribuie 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 = 0Spre 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.
Unele limbi, cum ar fi Ruby și Python , acceptă o sintaxă extinsă de atribuire numită atribuire paralelă:
a , b = 1 , 11Se 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 );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 ||= 10Acest construct atribuie o avaloare unei variabile numai dacă valoarea nu a fost încă atribuită sau este egală cu false.
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 + bSintaxa 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.).
Î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 . ++--
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 instrucțiuni pentru atribuire |