Cum se configurează smartphone-uri și PC-uri. Portal informativ

C # operații matematice. Expresii condiționale

Operatori relaționali și booleeni

În notație operator de relaţieși operator logic termen relaţieînseamnă o relație care poate exista între două sensuri și termen logic- relația dintre valorile logice „adevărat” și „fals”. Și pentru că operatorii relaționali dau rezultate adevărate sau false, aceștia sunt adesea folosiți împreună cu operatorii logici. Din acest motiv sunt considerate împreună.

Următorii sunt operatorii relaționali:

Operatorii booleeni includ următorii:

Rezultatul executării unui operator de relație sau a unui operator logic este o valoare booleană de tip bool.

În general, obiectele pot fi comparate pentru egalitate sau inegalitate folosind operatorii relaționali == și! =. Operatorii de comparație, =, pot fi aplicați numai tipurilor de date care acceptă o relație de ordonare. Prin urmare, operatorii relaționali pot fi aplicați tuturor tipurilor de date numerice. Dar valorile bool pot fi comparate doar pentru egalitate sau inegalitate, deoarece valorile adevărate și false nu sunt ordonate. De exemplu, comparația true> false în C # este lipsită de sens.

Luați în considerare un exemplu de program care demonstrează utilizarea operatorilor relaționali și logici:

Utilizarea sistemului; folosind System.Collections.Generic; folosind System.Linq; folosind System.Text; namespace ConsoleApplication1 (clasa Program (static void Main (șir argumente)) (scurt d = 10, f = 12; bool var1 = true, var2 = false; if (df) Console.WriteLine ("d> f"); // Compara variabilele var1 și var2 dacă (var1 și var2) Console.WriteLine ("Acest text nu va fi afișat"); dacă (! (var1 și var2)) Console.WriteLine ("! (var1 și var2) = adevărat"); dacă (var1 | var2) Console.WriteLine ("var1 | var2 = adevărat"); if (var1 ^ var2) Console.WriteLine ("var1 ^ var2 = adevărat"); Console.ReadLine ();)))

Operatorii booleeni în C # efectuează cele mai comune operații logice. Cu toate acestea, există o serie de operații efectuate conform regulilor logicii formale. Aceste operații booleene pot fi construite folosind operatori booleeni acceptați în C #. Prin urmare, C # oferă un astfel de set de operatori logici, care este suficient pentru a construi aproape orice operație logică, inclusiv implicațiile. Implicare este o operație binară care se evaluează ca fals numai dacă operandul său din stânga este adevărat și din dreapta este fals. (Operația de implicare reflectă următorul principiu: adevărul nu poate însemna fals.)

Operația de implicare poate fi construită pe baza unei combinații de operatori logici! și |:

Operatori logici scurtați

C # oferă și unele speciale, scurtat, variante ale operatorilor logici AND și OR, menite să obțină cod mai eficient. Să explicăm acest lucru cu următoarele exemple de operații logice. Dacă primul operand al unei operații logice AND este fals, atunci rezultatul său va fi fals indiferent de valoarea celui de-al doilea operand. Dacă primul operand al unei operații logice SAU are o valoare adevărată (adevărat), atunci rezultatul său va avea o valoare adevărată indiferent de valoarea celui de-al doilea operand. Datorită faptului că valoarea celui de-al doilea operand din aceste operații nu trebuie calculată, economisește timp și îmbunătățește eficiența codului.

O operație logică și trunchiată este efectuată folosind operator &&, și o operație SAU logică scurtată folosind operator ||... Acești operatori logici prescurtați corespund operatorilor logici uzuali & și |. Singura diferență dintre un operator logic trunchiat și unul obișnuit este că cel de-al doilea operand al său este evaluat numai după cum este necesar.

Ultima actualizare: 19.06.2017

C # folosește majoritatea operațiunilor care sunt utilizate în alte limbaje de programare. Operațiile reprezintă acțiuni specifice asupra operanzilor care sunt participanți la operație. Operandul poate fi o variabilă sau o anumită valoare (de exemplu, un număr). Operațiile pot fi unare (efectuate pe un operand), binare - pe doi operanzi și ternare - efectuate pe trei operanzi. Să luăm în considerare toate tipurile de operațiuni.

Operatii aritmetice binare:

    Operația de adunare a două numere:

    Int x = 10; int z = x + 12; // 22

    Operația de scădere a două numere:

    Int x = 10; int z = x - 6; // 4

    Operația de înmulțire a două numere:

    Int x = 10; int z = x * 5; // 50

    operația de împărțire a două numere:

    Int x = 10; int z = x / 5; // 2 dublu a = 10; dublu b = 3; dublu c = a / b; // 3,33333333

    Când împărțiți, rețineți că, dacă ambii operanzi reprezintă numere întregi, atunci rezultatul va fi, de asemenea, rotunjit la un număr întreg:

    Dublu z = 10/4; // rezultatul este 2

    În ciuda faptului că rezultatul operației este în cele din urmă plasat într-o variabilă de tip dublu, care vă permite să păstrați partea fracțională, operația în sine implică două literale, care sunt tratate implicit ca obiecte int, adică numere întregi, iar rezultatul va fi același număr întreg.

    Pentru a ieși din această situație, este necesar să definiți literale sau variabile care participă la operație, exact ca tipuri duble sau float:

    Dublu z = 10,0 / 4,0; // rezultatul este 2,5

    Operația de obținere a restului unei împărțiri întregi a două numere:

    x dublu = 10,0; dublu z = x% 4,0; // rezultatul este 2

Există, de asemenea, o serie de operații unare la care ia parte un operand:

    Operație de creștere

    Incrementul poate fi prefixat: ++ x - mai întâi, valoarea variabilei x este mărită cu 1, iar apoi valoarea acesteia este returnată ca rezultat al operației.

    Și există, de asemenea, un increment postfix: x ++ - mai întâi, valoarea variabilei x este returnată ca rezultat al operației, iar apoi i se adaugă 1.

int x1 = 5; int z1 = ++ x1; // z1 = 6; x1 = 6 Console.WriteLine ($ "(x1) - (z1)"); int x2 = 5; int z2 = x2 ++; // z2 = 5; x2 = 6 Console.WriteLine ($ "(x2) - (z2)");

Operatia de decrementare sau decrementare a unei valori cu unu. Există, de asemenea, un prefix decrement (--x) și un postfix (x--).

Int x1 = 5; int z1 = --x1; // z1 = 4; x1 = 4 Console.WriteLine ($ "(x1) - (z1)"); int x2 = 5; int z2 = x2--; // z2 = 5; x2 = 4 Console.WriteLine ($ "(x2) - (z2)");

Când efectuați mai multe operații aritmetice simultan, luați în considerare ordinea în care sunt efectuate. Prioritatea operațiunilor de la cea mai mare la cea mai mică:

    Crește, scade

    Înmulțirea, împărțirea, obținerea restului

    Adunare, scădere

Parantezele sunt folosite pentru a schimba ordinea operațiilor.

Luați în considerare un set de operații:

Int a = 3; int b = 5; int c = 40; int d = c --- b * a; // a = 3 b = 5 c = 39 d = 25 Console.WriteLine ($ "a = (a) b = (b) c = (c) d = (d)");

Aici avem de-a face cu trei operații: decrementare, scădere și înmulțire. Mai întâi, c este decrementat, apoi b * a este înmulțit și apoi scade. Adică, de fapt, setul de operații arăta astfel:

Int d = (c -) - (b * a);

Dar, cu ajutorul parantezelor, am putea schimba ordinea operațiilor, de exemplu, după cum urmează:

Int a = 3; int b = 5; int c = 40; int d = (c - (- b)) * a; // a = 3 b = 4 c = 40 d = 108 Console.WriteLine ($ "a = (a) b = (b) c = (c) d = (d)");

Asociativitatea operatorului

După cum sa menționat mai sus, operațiile de înmulțire și împărțire au aceeași prioritate, dar atunci care va fi rezultatul în expresia:

Int x = 10/5 * 2;

Ar trebui să interpretăm această expresie ca (10/5) * 2 sau ca 10 / (5 * 2)? Într-adevăr, în funcție de interpretare, vom obține rezultate diferite.

Când operaţiile au aceeaşi prioritate, ordinea evaluării este determinată de asociativitatea operatorilor. Există două tipuri de operatori în funcție de asociativitate:

    Operatori asociativi stânga care se execută de la stânga la dreapta

    Operatori asociativi de dreapta care se execută de la dreapta la stânga

Toți operatorii aritmetici (cu excepția prefixului de creștere și decrementare) sunt asociativi la stânga, adică sunt executați de la stânga la dreapta. Prin urmare, expresia 10/5 * 2 trebuie interpretată ca (10/5) * 2, adică rezultatul va fi 4.

Nu există conversii booleene implicite în C #, chiar și pentru tipurile aritmetice întregi. Prin urmare, notația este destul de corectă în limbajul C++:

intk1 = 7;
dacă (k1) Consolă. WriteLine(" Bine!");

ilegal în programele C #. În etapa de traducere, va apărea o eroare deoarece condiția calculată are tipul int, și conversia implicită a acestui tip în tip bool absent.

În C #, reguli mai stricte se aplică și operațiilor logice. Deci, intrarea dacă(k1 && (X> y)), corect în C ++, are ca rezultat o eroare în

Programele C # deoarece && este definit doar pentru operanzi de tip bool, iar în această expresie, unul dintre operanzi este de tip int. În limbajul C #, în aceste situații, ar trebui să utilizați următoarele intrări:

dacă(k1>0)
dacă((k1>0) && (X> y))

Operațiile logice sunt împărțite în două categorii: unele sunt efectuate pe valorile booleene ale operanzilor, în timp ce altele efectuează o operație booleană asupra biților operanzilor. Din acest motiv, există doi operatori unari de negație în C # — negație logică, specificată de operatorul! și negație pe biți, specificată de operatorul ~. Primul este definit pe un operand de tip bool, al doilea - peste un operand de tip întreg, începând cu tipul int si mai sus (int, uint, lung, ulong). Rezultatul operației în al doilea caz este un operand în care fiecare bit este înlocuit cu complementul său. Să dăm un exemplu:

/// < rezumat>
/// Expresii booleene
/// rezumat>
publicgolLogică() {
// operații de negație ~,!
bool b1, b2;
b1= 2*2 == 4;
b2= b1;
// b2 = ~ b1;
uint j1= 7, j2;
j2= ~ j1;
// j2= !j1;
int j4= 7, j5;
j5= ~ j4;
Console.WriteLine ("uint j2= " + j2+ " int j5= " + j5);
} // Logică

În acest fragment, declarațiile care duc la erori sunt comentate. În primul caz, s-a încercat să se aplice operația de negație pe biți la o expresie similară bool, în al doilea, negația logică a fost aplicată datelor întregi. Ambele sunt ilegale în C #. Observați diferitele interpretări ale negației pe biți pentru tipurile întregi fără semn și semnate. Pentru variabile j5 și j2 șirul de biți care definește valoarea este același, dar interpretat diferit. Concluzia corespunzătoare este următoarea:

uintj2 = 4294967288
intj5 = -8.

Operații logice binare" && - condițional ȘI „și” || - SAU condiționat „sunt definite numai asupra datelor de tip bool. Operațiile se numesc condiționate sau scurte, deoarece calculul celui de-al doilea operand depinde de valoarea deja calculată a primului operand. Valoarea operațiilor logice condiționate constă în eficiența lor în ceea ce privește timpul de execuție. Adesea ele vă permit să evaluați o expresie logică care are sens, dar în care al doilea operand este nedefinit. Să luăm ca exemplu problema clasică a căutării după un model într-un tablou, atunci când se caută un element cu o valoare dată (pattern). Poate să existe sau nu un astfel de element în matrice. Iată o soluție tipică la această problemă într-o formă simplificată, dar transmite esența problemei:

// CondiţionalȘi- &&
int [] ar= { 1, 2, 3 };
căutare int= 7;
int i= 0;
in timp ce eu< ar.Lungime)&& (ar [i]!= căutare)){
i ++;
}
dacă eu< ar.Lungime)Console.WriteLine ("Probăgăsite");
altfelConsole.WriteLine ("Probănugăsite");

Dacă valoarea variabilei căutare(model) nu se potrivește cu niciuna dintre valorile elementelor matricei ar, apoi ultima verificare a stării buclei in timp ce va fi executat dacă valoarea i, egal ar. Lungime. În acest caz, primul operand va primi valoarea fals, și deși al doilea operand nu este definit, bucla se va termina normal. Al doilea operand este nedefinit la ultima verificare deoarece indexul elementului de matrice este în afara intervalului (în C #, indexarea elementului începe de la zero).

Trei operații binare pe biți - „& - AND”, „| - SAU "," ^ - XOR "sunt folosite în două moduri. Ele sunt definite ca tipurile întregi de mai sus int, și peste tipuri booleene. În primul caz, ele sunt folosite ca operații pe biți, în al doilea, ca operații logice obișnuite. Uneori este necesar ca ambii operanzi să fie calculati în orice caz, atunci aceste operații nu pot fi evitate. Iată un exemplu de utilizare a acestora pentru prima dată:

// Operații logice pe bițiȘi, Sau, XOR(&,|,^)
int k2= 7, k3= 5, k4, k5, k6;
k4= k2& k3;
k5= k2 | k3;
k6= k2 ^k3;
Console.WriteLine ("k4= " + k4+ " k5= " + k5+ " k6= " + k6);

Rezultate de ieșire:

k4 = 5 k5 = 7 k6 =2

Iată un exemplu de căutare a unui model folosind ȘI logic: i= 0;

căutare= ar;
in timp ce eu< ar.Lungime)& (ar [i]!= căutare)) i ++;
dacă eu< ar.Lungime)Console.WriteLine ("Probăgăsite");
altfel cConsole.WriteLine ("Probănugăsite");

Acest fragment este garantat să aibă un model de căutare în matrice, iar fragmentul va fi executat cu succes. În aceleași cazuri când tabloul nu conține un element căutare, se va arunca o excepție. Sensul semnificativ al unei astfel de proceduri - apariția unei excepții - poate fi un semn al unei erori în date, care necesită o gestionare specială a situației.

Această secțiune descrie pe scurt sintaxa și aplicarea tuturor operațiilor C #, cu excepția unora primare, care sunt discutate mai târziu în cursul materialului aferent.

Creștere și descreștere

Operațiile de creștere (++) și decrementare (-) incrementează și decrementează operandul cu unu. Au două forme de notație - prefix când semnul operației este scris înaintea operandului și postfix... În formă de prefix, operandul este mai întâi schimbat, iar apoi valoarea lui devine valoarea rezultantă a expresiei, iar în formă de postfix, valoarea expresiei este valoarea inițială a operandului, după care este schimbată.

Operații standard de creștere există pentru valori întregi, simbolice, reale și financiare.

Operațiune nouă

Noua operațiune este folosită pentru a crea un nou obiect. Format de operare:

tip nou ([argumente])

Cu această operație, puteți crea obiecte atât de tip referință, cât și de tip valoare, de exemplu:

obiect z = obiect nou (); int i = new int (); // la fel ca int i = 0;

Când se efectuează noua operațiune, este mai întâi alocată cantitatea necesară de memorie (pentru tipurile de referință în heap, pentru tipurile de valori - pe stivă), apoi așa-numita constructor implicit, adică metoda prin care obiectul este inițializat. Se atribuie o variabilă de tip valoare Mod implicit, care este egal cu zero de tipul corespunzător.

Operații de negație

Negație aritmetică(unar minus -) inversează semnul operandului. Operatorul standard de negație este definit pentru tipurile int, long, float, double și zecimal. Poate fi aplicat valorilor de alte tipuri dacă este posibilă conversia implicită la aceste tipuri.

Logic negare(!) este definit pentru tipul bool. Rezultatul operației este fals dacă operandul este adevărat și adevărat dacă operandul este fals.

Negație pe biți(~), denumită adesea biți, inversează fiecare bit în reprezentarea binară a unui operand int, uint, long sau ulong.

Conversie explicită de tip

O operație este folosită pentru a converti explicit o valoare de la un tip la altul. Acest lucru este necesar atunci când nu există o conversie implicită. Pierderea de informații este posibilă la conversia de la un tip mai lung la unul mai scurt. Format de operare:

(tip) expresie

Aici, tip este numele tipului la care se efectuează conversia, iar expresia este cel mai adesea numele unei variabile, de exemplu:

lung b = 300; int a = (int) b; // datele nu se pierd int d = (octet) a; // datele sunt pierdute

Înmulțirea, împărțirea și restul împărțirii

Operație de înmulțire(*) returnează rezultatul înmulțirii a doi operanzi. Operația de înmulțire standard este definită pentru tipurile int, uint, long, ulong, float, double și zecimal. Poate fi aplicat valorilor de alte tipuri dacă este posibilă conversia implicită la aceste tipuri. Tipul rezultatului operației este egal cu „cel mai mare” dintre tipurile de operanzi, dar nu mai mic decât int.

Tabelul 3.2. Simbolurile x și y denotă valori pozitive finite, simbolul z - rezultatul unei operații reale de înmulțire. Dacă rezultatul este prea mare pentru a fi reprezentat de tipul dat, se presupune că este „infinit”, dacă este prea mic, este luat la 0. NaN (nu un număr) înseamnă că rezultatul nu este un număr.

Tabelul 3.2. Rezultate reale înmulțirii
* + y -y +0 -0 + - NaN
+ x + z -z +0 -0 + - NaN
-X -z + z -0 +0 - + NaN
+0 +0 -0 +0 -0 NaN NaN NaN
-0 -0 +0 -0 +0 NaN NaN NaN
+ + - NaN NaN + - NaN
- - + NaN NaN - + NaN
NaN NaN NaN NaN NaN NaN NaN NaN

Operarea diviziei(/) calculează câtul primului operand împărțit la al doilea. Operația standard de împărțire este definită pentru tipurile int, uint, long, ulong, float, double și zecimal. Poate fi aplicat valorilor de alte tipuri dacă există o conversie implicită la aceste tipuri pentru ele. Tipul rezultatului este determinat de regulile de conversie, dar nu mai puțin de int.

Dacă ambii operanzi sunt numere întregi, rezultatul operației este rotunjit în jos la cel mai apropiat număr întreg. Dacă divizorul este zero, este aruncată o excepție System.DivideByZeroException.

Dacă cel puțin unul dintre operanzi este real, partea fracțională a rezultatului diviziunii nu este eliminată, iar toate valorile posibile sunt prezentate în tabelul 3.3.

Tabelul 3.3. Rezultate reale de divizie
/ + y -y +0 -0 + - NaN
+ x + z -z + - +0 -0 NaN
-X -z + z - + -0 +0 NaN
+0 +0 -0 NaN NaN +0 -0 NaN
-0 -0 +0 NaN NaN -0 +0 NaN
+ + - + - NaN NaN NaN
- - + - + NaN NaN NaN
NaN NaN NaN NaN NaN NaN NaN NaN

Pentru valorile financiare (de tip zecimal), împărțirea cu 0 și depășirea, se aruncă excepții corespunzătoare; dispariția ordinii rezultatul este 0.

Modul de funcționare(%) este, de asemenea, interpretat diferit pentru valorile întregi, reale și financiare. Dacă ambii operanzi sunt numere întregi, rezultatul operației este x - (x / y) * y. Dacă divizorul este zero, este aruncată o excepție System.DivideByZeroException.

Dacă cel puțin unul dintre operanzi este real, rezultatul operației este calculat prin formula x - n * y, unde n este cel mai mare număr întreg mai mic sau egal cu rezultatul împărțirii x la y. Toate combinațiile posibile de valori ale operanzilor sunt prezentate în Tabelul 3.4.

Tabelul 3.4. Rezultate reale rămase
% + y -y +0 -0 + - NaN
+ x + z z NaN NaN X X NaN
-X -z -z NaN NaN -X -X NaN
+0 +0 +0 NaN NaN +0 +0 NaN
-0 -0 -0 NaN NaN -0 -0 NaN
+ NaN NaN NaN NaN NaN NaN NaN
- NaN NaN NaN NaN NaN NaN NaN
NaN NaN NaN NaN NaN NaN NaN NaN

Pentru valorile financiare (de tip zecimal), la obținerea restului de la împărțirea cu 0 și depășirea, se aruncă excepții corespunzătoare, când dispariția ordinii rezultatul este 0. Semnul rezultatului este semnul primului operand.

Adunare si scadere

Operație de adăugare(+) returnează suma a doi operanzi. Operația de adăugare standard este definită pentru tipurile int, uint, long, ulong, float, double și zecimal. Poate fi aplicat valorilor de alte tipuri dacă există o conversie implicită la aceste tipuri pentru ele. Tipul rezultatului operației este egal cu „cel mai mare” dintre tipurile de operanzi, dar nu mai mic decât int.

Toate valorile posibile pentru operanzi reali sunt prezentate în Tabelul 3.5.

Tabelul 3.5. Rezultate adaosuri reale
+ y +0 -0 + - NaN
X z X X + - NaN
+0 y +0 +0 + - NaN
-0 y +0 -0 + - NaN
+ + + + + NaN NaN
- - - - NaN - NaN
NaN NaN NaN NaN NaN NaN NaN

Operația de scădere(-) returnează diferența a doi operanzi. Operația standard de scădere este definită pentru tipurile int, uint, long, ulong, float, double și zecimal. Poate fi aplicat valorilor de alte tipuri dacă există o conversie implicită la aceste tipuri pentru ele. Tipul rezultatului operației este egal cu „cel mai mare” dintre tipurile de operanzi, dar nu mai mic decât int.

Dacă ambii operanzi sunt întregi sau zecimale, iar rezultatul operației este prea mare pentru a fi reprezentat de tipul specificat, este aruncată o excepție System.OverflowException.

Toate valorile posibile ale rezultatului scăderii pentru operanzi reali sunt prezentate în tabelul 3.6. Simbolurile x și y denotă valori pozitive finite, simbolul z denotă rezultatul unei operații de scădere reală. Dacă x și y sunt egali, rezultatul este zero pozitiv. Dacă rezultatul este prea mare pentru a fi reprezentat de tipul dat, este luat egal cu valoarea „infinit” cu același semn ca x - y, dacă este prea mic, este luat ca 0 cu același semn ca x - y .

Tabelul 3.6. Rezultate reale de scădere
- y +0 -0 + - NaN
X z X X - + NaN
+0 -y +0 +0 - + NaN
-0 -y -0 +0 - + NaN
+ + + + NaN + NaN
- - - - - NaN NaN
NaN NaN NaN NaN NaN NaN NaN

Operații în schimburi

Operații în schimburi (<< и >>) sunt aplicate operanzilor întregi. Acestea mută reprezentarea binară a primului operand la stânga sau la dreapta cu numărul de biți specificat de al doilea operand.

La schimbare la stânga (<< ) освободившиеся разряды обнуляются. При schimba la dreapta(>>) biții eliberați sunt umpluți cu zerouri dacă primul operand este fără semn și, în caz contrar, este semnat. Operațiile standard de schimbare sunt definite pentru tipurile int, uint, long și ulong.

Operații de relație și teste de egalitate

Operațiuni de relație (< , <= , >,> =, ==,! =) compară primul operand cu al doilea. Operanzii trebuie să fie de tip aritmetic. Rezultatul operației este un tip boolean, egal cu adevărat sau fals. Regulile de calcul a rezultatelor sunt date în> y

adevărat dacă x este mai mare decât y, fals în caz contrar X<= y adevărat dacă x este mai mic sau egal cu y, fals în caz contrar x> = y adevărat dacă x este mai mare sau egal cu y, fals în caz contrar

Operații logice pe biți

Operații logice pe biți(&, |, ^) se aplică operanzilor întregi și operează pe reprezentările lor binare. La efectuarea operațiilor, operanzii sunt potriviți bit cu bit (primul bit al primului operand cu primul bit al celui de-al doilea, al doilea bit al primului operand cu al doilea bit al celui de-al doilea etc.). Operațiile standard sunt definite pentru tipurile int, uint, long și ulong.

La conjuncție pe biți(&), bitul rezultat este 1 numai atunci când biții corespunzători ambilor operanzi sunt 1.

La disjuncție pe biți(|), bitul rezultat este 1 dacă bitul corespunzător a cel puțin unuia dintre operanzi este 1.

Pentru SAU exclusiv pe biți() bitul rezultat este 1 numai atunci când bitul corespunzător doar unuia dintre operanzi este 1.

Operații logice condiționate

Operații logice condiționateȘI (&&) și SAU (||) sunt cel mai adesea folosite cu operanzi booleeni. Rezultatul unei operații logice este adevărat adevărat, atunci rezultatul operației condiționate va fi valoarea celui de-al doilea operand, în caz contrar - valoarea celui de-al treilea operand. Fie al doilea operand, fie al treilea este întotdeauna evaluat. Tipul lor poate varia.

Tipul rezultatului operației depinde de tipul celui de-al doilea și al treilea operand. Dacă operanzii sunt de același tip, acesta devine tipul rezultatului operației.

Operațiuni de atribuire

Operațiuni de atribuire(=, + =, - =, * =, etc.) setează o nouă valoare variabilei. Aceste operațiuni pot fi utilizate în program ca instrucțiuni complete.

Format de operare sarcina simplă (= ):

variabilă = expresie

Mecanismul de realizare a operației de atribuire este următorul: expresia este evaluată și rezultatul acesteia este stocat în memorie la adresa, care este determinată de numele variabilei din stânga semnului operației. Ceea ce a fost stocat anterior în această zonă de memorie se pierde. Exemple de operatori de atribuire:

adunare cu atribuire, primul se adaugă celui de-al doilea operand, iar rezultatul este scris la primul operand, adică expresia a + = b este o expresie mai compactă a expresiei a = a + b.

Rezultatul unei operații complexe de atribuire este valoarea scrisă în operandul din stânga.

Operațiuni de atribuire drept-asociativ, adică se execută de la dreapta la stânga, spre deosebire de majoritatea celorlalte operații (a = b = c înseamnă a = (b = c)).

Întrebări și sarcini pentru munca independentă a elevului

  1. Unde puteți descrie variabile? Ce este inclus în descrierea unei variabile?
  2. Ce se întâmplă când utilizați diferite tipuri de operanzi într-o expresie? Dă exemple.
  3. Listați C # operațiuni după prioritate.
  4. Ce este NaN? În ce operații rezultă NaN?
  5. La ce tip de operanzi sunt aplicabile operațiunile de schimbare?
  6. Ce sunt excepțiile?
  7. Descrieți principiile gestionării excepțiilor.

CAPITOLUL 10. Expresii și operatori

În acest capitol, ne vom uita la fundamentul oricărui limbaj de programare - capacitatea sa de a efectua atribuiri și comparații folosind operatori. Vom vedea ce operatori sunt în C # și care este precedența lor, apoi vom explora categoriile individuale de expresii pentru efectuarea de operații aritmetice, alocarea de valori și compararea operanzilor.

Operatori

Un operator este un simbol care indică o operație care trebuie efectuată pe unul sau mai multe argumente. Când instrucțiunea este executată, se obține rezultatul. Sintaxa de utilizare a operatorilor este ușor diferită de metodele de apelare și ar trebui să cunoașteți formatul expresiilor care conțin operatori în C # ca dosul mâinii. Ca și în majoritatea celorlalte limbi, semantica operatorilor în C # urmează regulile și notațiile familiare nouă de la școală. Operatorii de bază în C # includ înmulțirea (*), împărțirea (/), adunarea și plusul unar (+), scăderea și minusul unar (-), modulul (%) și atribuirea (=).

Operatorii sunt folosiți pentru a obține o nouă valoare din valorile pe care se operează. Aceste valori inițiale sunt numite operanzi. Rezultatul operației trebuie să fie stocat în memorie. Uneori este stocat într-o variabilă care conține unul dintre operanzii originali. Compilatorul C # generează un mesaj de eroare dacă o nouă valoare nu este definită sau stocată atunci când se utilizează un operator. Codul de mai jos nu modifică valorile. Compilatorul va emite un mesaj de eroare deoarece o expresie aritmetică care nu modifică cel puțin o valoare este de obicei considerată o eroare.

clasa NoResultApp

{

public static void Principal ()

{

int i; int j;

i + j; // Eroare pentru că rezultatul nu este atribuit la nimic. )>

Majoritatea operatorilor lucrează numai cu tipuri de date numerice, cum ar fi octet, scurt, lung, întreg, simplu, dubluși Zecimal. O excepție sunt operatorii de comparație (== și! =). De asemenea, în C #, puteți utiliza operatorii + și - pentru clasă Şirși chiar folosiți operatorii de increment (++) și (-) pentru constructe de limbaj neobișnuite, cum ar fi delegații. Voi vorbi despre acesta din urmă în capitolul 14.

Vechimea operatorului

Când există mai multe instrucțiuni într-o expresie, compilatorul trebuie să determine ordinea executării lor. În acest caz, compilatorul este ghidat de regulile numite vechimea operatorilor.Înțelegerea priorității operatorilor este necesară pentru scrierea corectă a expresiilor - uneori rezultatul poate să nu fie cel așteptat.

Luați în considerare expresia 42 + 6 * 10. Dacă adăugați 42 și 6, și apoi înmulțiți suma cu 10, obțineți 480. Dacă înmulțiți 6 cu 10 și adăugați 42 la rezultat, obțineți 102. Când compilați codul, o componentă specială a compilatorului este - analizor lexical - este responsabil pentru ordinea în care este citit acest cod. Analizorul lexical este cel care determină precedența relativă a operatorilor eterogene într-o expresie. Pentru a face acest lucru, folosește o anumită valoare - prioritatea - a fiecărui operator suportat. Operatorii cu prioritate mai mare sunt acceptați mai întâi. În exemplul nostru, operatorul * are prioritate față de operatorul +, deoarece * absoarbe(acum voi explica acest termen) operanzii mei înainte de a face +. Explicația constă în regulile generale de aritmetică: înmulțirea și împărțirea. mereu are prioritate față de adunarea și scăderea. Să revenim la exemplu: ei spun că numărul 6 inghitit operator * atât în ​​42 + 6 * 10, cât și în 42 * 6 + 10, deci aceste expresii sunt echivalente cu 42 + (6 * 10) și (42 * 6) + 10.

Cum este definită vechimea în C #

Acum să vedem cum este definită precedența operatorului în C #. Operatorii sunt listați mai jos în ordinea descrescătoare a priorității (Tabelul 10-1). Voi intra în mai multe detalii despre diferitele categorii de operatori acceptați în C # mai târziu.

Tab. 10-1. Precedența operatorului în C #.

Categoria operatorului Operatori
Simplu (x), xy, f (x), a [x], x ++, x -, nou, typeof, sizeof, bifat, nebifat
Unar +, -,!, ++ x, - x, (T) x
Multiplicativ *,/, %
Aditiv +, -
Schimb «, »
Atitudine <, >, <=, >=, este
Egalitate ==
ȘI logic (ȘI) &
SAU exclusiv logic (XOR) ^
SAU logic (SAU) 1
ȘI condiționat &&
IL și (SAU) condiționat II
Condiție 9-
Misiune = *= /= % = , + = , -= « = , » = , &=, ^ = , =

Asociativitatea stângă și dreaptă

Asociativitatea determină care parte a expresiei trebuie evaluată mai întâi. De exemplu, rezultatul expresiei de mai sus poate fi 21 sau 33, în funcție de asociativitatea care va fi folosită pentru operatorul „-”: stânga sau dreapta.

Operator - are asociativitate stângă, adică mai întâi se calculează 42-15, iar apoi se scade din rezultat 6. Dacă ar avea asociativitate dreaptă, mai întâi s-ar calcula partea dreaptă a expresiei (15-6) și apoi rezultatul ar fi scăzut din 42.

Toți operatorii binari (operatori cu doi operanzi), cu excepția operatorilor de atribuire, - stânga-asociativ, adică procesează expresiile de la stânga la dreapta. Prin urmare, a + b+ cu - la fel ca (a + b) + c, unde se calculează mai întâi a + b,și apoi se adună suma cu. Operatori de atribuire și condiționali - dreapta-asociativa, adică procesează expresiile de la dreapta la stânga. Cu alte cuvinte, a = b = c echivalentă cu a = (b= cu). Mulți oameni dau peste asta atunci când doresc să pună mai multe instrucțiuni de atribuire pe o singură linie, așa că să ne uităm la acest cod:

folosind System;

clasa RightAssocApp (

public static void Principal () (

int a = 1; int b = 2; int c = 3;

Consolă.WriteLine ("a = (0) b = (1) c = (2>", a, b, c); a = b = c;

Consola.WriteLine ("După" a = b = c -: a = (0) b = (1) c = (2) ", a, b, c);>>

Rezultatul rulării acestui exemplu este următorul:

a = 1 b = 2 c = 3

După „a = b = c”: a = 3 b = 3 o = 3

Evaluarea expresiilor de la dreapta la stânga poate fi confuză la început, dar să o abordăm astfel: dacă operatorul de atribuire ar fi lăsat asociativ, compilatorul ar trebui mai întâi să evalueze a = b, atunci A ar fi egal cu 2 și apoi b= s și ca urmare b ar fi 3. Rezultatul final ar fi a = 2 b = 3 c = 3. Evident, la asta nu ne așteptăm când scriem A= b= cu,și de aceea operatorii de atribuire și condiționali sunt asociativi drept.

Uz practic

Nimic nu este mai supărător decât găsirea unei greșeli făcute doar pentru că dezvoltatorul nu cunoștea regulile de precedență și asociativitate. Am întâlnit mesaje în conferințe prin e-mail în care oameni aparent rezonabili au propus un fel de mecanism de auto-documentare - utilizarea spațiilor pentru a indica operatorilor că, in opinia lor, au vechime. De exemplu, din moment ce știm că operatorul de înmulțire are prioritate față de operatorul de adunare, ar trebui să scriem așa ceva, în care spațiile indică subînțeles vechime in munca:

a = b * c + d;

Această abordare este fundamental greșită: compilatorul nu poate analiza corect codul dacă sintaxa specifică nu este definită. Compilatorul analizează codul conform regulilor definite de dezvoltatorii compilatorului. Pe de altă parte, există paranteze folosite pentru a indica în mod explicit precedența și asociativitatea. De exemplu, expresia A= b * c + d poate fi rescris ca a = (b * c) + d sau cum A= b * (c + d) iar compilatorul va evalua mai întâi expresia între paranteze. Dacă există mai multe perechi de paranteze, compilatorul va evalua mai întâi expresiile între paranteze, iar apoi întreaga expresie, pe baza regulilor de precedență și asociativitate descrise.

Convingerea mea fermă este că parantezele ar trebui folosite întotdeauna atunci când există mai mulți operatori într-o expresie. Vă recomand să faceți acest lucru chiar dacă înțelegeți ordinea calculelor, deoarece persoanele care vă vor menține codul s-ar putea să nu fie la fel de alfabetizate.

C # operatori

Cel mai corect este să luăm în considerare operatorii după vechime. Mai jos voi descrie cei mai comuni operatori.

Operatori simpli

  • (x) Aceasta este o variație a operatorului de paranteză pentru controlul ordinii calculelor, atât în ​​operațiile matematice, cât și în apelurile de metode.
  • xy Operatorul punct este folosit pentru a indica un membru al unei clase sau structuri. Aici NS reprezintă o entitate care conține un membru la.
  • f (x) Acest tip de operator de paranteză este folosit pentru a enumera argumentele metodei.
  • a [x] Parantezele pătrate sunt folosite pentru a indexa o matrice. Aceste paranteze sunt, de asemenea, folosite împreună cu indexatoarele atunci când obiectele pot fi tratate ca o matrice. Pentru indexare, vezi capitolul 7.
  • х ++ Despre operatorul de creștere vom vorbi separat în secțiunea „Operatori de creștere și decreștere”.
  • x- Operatorul de decrementare va fi discutat mai târziu.
  • new Acest operator este folosit pentru a instanția obiecte pe baza unei definiții de clasă.

tip de

Reflecţie(reflecție) este capacitatea de a obține informații de tip în timpul execuției. Aceste informații includ numele tipurilor, claselor și membrilor structurii. În NET Framework această funcționalitate este asociată cu clasa Sistem. Tip. Această clasă este rădăcina tuturor operatorilor de reflexie și poate fi obținută folosind operatorul tip de. Nu vom intra în detaliile reflecției chiar acum (vom face asta în capitolul 16), dar iată un exemplu simplu pentru a ilustra cât de ușor este să utilizați operatorul tip de„pentru a obține aproape orice informații despre un tip sau obiect în timpul execuției programului:

folosind System;

folosind System.Reflection;

Apple clasa publica (

public int nSeeds;

vid public Ripen ()

{

> >

clasa publică TypeOfApp (

public static void Principal () (

Tip t = tip de (Mere);

șir className = t.ToStringO;

Console.IgShip ("\ nInformation 0 class (O)", className);

Console.WriteLine ("\ nMeroflH (0)", className); Consolă. WriteLine ("--------"); Metode Methodlnfo = t.GetMethodsO;

foreach (metoda MethodInfo în metode)

Console.WriteLine (method.ToSt ring ());

}

Console.WriteLine ("\nBce membri (O)", className); Consolă. Writel_ine ("--------"); Memberlnfo allMembers = t.GetMembersO; foreach (Memberlnfo membru în allMembers)

{

Consolă. WriteLine (member.ToStringO);

} > }

Acest program conține clasa măr care are doar doi membri: câmpul nSemințe si metoda Coace.În primul rând, folosind operatorul tip deși numele clasei primesc obiectul Sistem. Tip, care este apoi stocat într-o variabilă t. CUîn acest moment pot folosi obiectul Sistem. Tip pentru a obține toate metodele și membrii unei clase Măr. Acest lucru se face folosind metodele GetMethodsși Obțineți membri respectiv. Rezultatele acestor metode sunt scrise în ieșirea standard după cum urmează:

Informații despre clasă Apple Metode Apple

Int32 GetHashCodeQ

System.String ToStringQ

Void RipenO

System.Type GetTypeO

Toți membrii Apple

Int32 nSeeds

Int32 GetHashCodeO

Boolean Egal (System.Object)

System.String ToStringO

Void RipenO

System.Type GetTypeO

Înainte de a trece mai departe, vreau să fac două puncte. În primul rând, observați că membrii moșteniți ai clasei sunt, de asemenea, deduși. Deoarece clasa nu este derivată în mod explicit dintr-o altă clasă, știm că toți membrii nu sunt definiți în clasă măr moștenește din clasa de bază implicită Sistem.Obiect.În al doilea rând, obiectul Tip de sistem poate fi obtinut prin metoda GetType. Acesta este moștenit de la Sistem.Obiect metoda vă permite să lucrați cu obiecte, nu cu clase. Oricare dintre cele două fragmente de mai jos poate fi folosită pentru a obține un obiect Sistem. Tip.

II Obținerea unui obiect System.Type bazat pe definiția clasei. Tip t1 = tip de (Mere);

// Obține obiectul System.Type din obiect. Apple Apple = nou AppleQ; Tastați t2 = apple.GetTypeO;

dimensiunea

Operator dimensiunea folosit pentru a obține dimensiunea tipului specificat în octeți. În acest sens, țineți cont de doi factori extrem de importanți. La început, dimensiunea poate fi aplicat numai la tipurile dimensionale. Prin urmare, deși poate fi folosit pentru membrii clasei, nu poate fi folosit pentru clase ca atare. În al doilea rând, dimensiunea poate fi folosit numai în metode sau blocuri de cod marcate ca nesigure. CU Ne vom familiariza cu acest tip de cod în Capitolul 17. Iată un exemplu de utilizare a operatorului dimensiuneaîntr-o metodă a unei clase marcate ca nesigur:

folosind System;

clasa BasicTypes (

// Notă: Codul care utilizează operatorul sizeof // trebuie marcat ca nesigur, static nesigur public void ShowSizesQ (

Console.WriteLine ("\ nPa3Mephi principalele tipuri"); Console.WriteLine ("Pa3Mep scurt = (0)", sizeof (scurt)); Console.WriteLine ("Pa3Mep int = (0)", sizeof (int)); Console.Writel_ine ("Pa3Mep long = (0)", sizeof (long)); Console.WriteLine ("Pa3Mep bool = (0)", sizeof (bool)); ))

clasa UnsafeUpp

{

nesigur public static void MainQ

{

BasicTypes.ShowSizes ();

} }

Iată rezultatul acestei aplicații:

Dimensiunile tipurilor de bază Mărimea scurtă = 2 Mărimea int = 4 Mărimea lungă = 8 Mărimea bool = 1

Operator dimensiunea poate fi folosit pentru a defini dimensiuni nu numai pentru tipuri simple încorporate, ci și pentru tipuri de dimensiuni personalizate, cum ar fi structuri. Cu toate acestea, rezultatele dimensiunea poate să nu fie evident:

// Folosind operatorul sizeof. folosind System;

struct StructWithNoMembers

struct StructWithMembers

{

pantaloni scurti;

int i;

lung 1;

bool b; )

struct CompositeStruct

{

StructWithNoMembers a; StructWithMembers b;

StructWithNoMembers c; )

clasa UnSafe2App (

nesigur public static void Main () (Console.WriteLine ("structura \ nPa3Mep StructWithNoMembers = (0)),"

sizeof (StructWithNoMembers)); Console.WriteLine ("structura \ nPa3Mep StructWithMembers = (0)",

sizeof (StructWithMembers)); Console.WriteLine ("\ nPa3Mep Structura CompositeStruct = (0)",

sizeof (CompositeStruct)); ))

Deși se poate presupune că această aplicație va scoate 0 pentru o structură fără membri (StructWithNoMembers), 15 pentru o structură cu patru elemente de tipuri de bază (StructWithMembers)și 15 pentru o structură care le adună pe cele două anterioare (CompositeStruct), in realitate rezultatul va fi asa:

Dimensiune StructWithNoMembers structura = 1 Dimensiune StructWithMembers structura = 16

Dimensiunea structurii compozite = 24

Explicația pentru aceasta este modalitatea de a păstra constructul struct compilatorul în fișierul de ieșire unde compilatorul aplică justificarea și umplutura. De exemplu, dacă o structură are o dimensiune de 3 octeți și este aliniată pe limite de 4 octeți, compilatorul va adăuga automat 1 octet la structură, iar operatorul dimensiunea va indica faptul că dimensiunea structurii este de 4 octeți. Nu uitați să aveți în vedere acest lucru atunci când dimensionați structurile în C #.

verificatși nebifate

Acești doi operatori controlează verificarea depășirii atunci când efectuează operații matematice.

Operatori matematici

C #, ca majoritatea celorlalte limbaje, acceptă operatorii matematici de bază: înmulțire (*), împărțire (/), adunare (+), scădere (-) și modul (%). Scopul primilor patru operatori este clar din numele lor; operatorul modul formează restul unei diviziuni întregi. Iată câteva coduri pentru a ilustra utilizarea operatorilor matematici:

folosind System;

clasa MathOpsApp

{

public static void MainQ

{

// Clasa System.Random face parte din // biblioteca de clase .NET Framework. În constructorul său implicit, // metoda Next folosește data / ora curentă ca // valoare inițială. Random Rand = nou RandomO; int a, b, c;

a = rand.Următorul () % 100; // Valoarea limită 99. b = rand.NextO % 100; // Valoarea limită 99.

Consola.WriteLine ("a = (0) b = (1)", a, b);

c = a * b;

Console.WriteLineC "a * b = (0)", c);

// Rețineți că aici sunt folosite numere întregi. // Prin urmare, dacă a este mai mic decât b, rezultatul va fi întotdeauna // 0. Pentru a obține un rezultat mai precis // trebuie să utilizați variabile de tip double sau float, c = a / b; Consola.WriteLineC "a / b = (0)", c);

Consola.WriteLineC "a + b = (0)", c);

Console.WriteLineC "a - b = (0)", c);

Consola.WriteLineC "a X b = (0)", c); >>

Operatori unari

Există doi operatori unari: plus și minus. Operatorul unar minus îi spune compilatorului că numărul este negativ. Deci, în următorul cod A va fi egal cu -42:

folosind System; folosind System;

clasa UnarylApp (

public static void Principal ()

{

int a = 0;

a = -42;

Console.WriteLine ("(0)", a); ))

Totuși, ambiguitatea apare în acest cod: folosind System;

clasa Unary2App<

public static void Principal () (

int a; int b = 2; int c = 42;

a = b * -c;

Console.WriteLine ("(0)", a); >>

Expresie A= b * -c nu tocmai clar. Din nou, folosirea parantezelor va clarifica această expresie:

// Când folosim paranteze, este evident că // înmulțim b cu un număr negativ c. a = b * (-c);

Dacă unar minus returnează o valoare a operandului negativ, ați putea crede că unar plus returnează pozitiv. Cu toate acestea, plusul unar returnează doar operandul în forma sa originală și nu face nimic altceva, adică nu afectează operandul. De exemplu, rularea acestui cod va scoate valoarea -84:

folosind System;

clasa UnarySApp (

public static void MainQ (

a = b * (+ c);

Console.WriteLine ("(0)", a); ))

Pentru a obține o valoare pozitivă, utilizați funcția Matematică.Abs. Acest cod va tipări valoarea 84:

folosind System;

clasa Unary4App

{

public static void Principal ()

int a; int b = 2; int c = -42;

a = b * Math.Abs ​​​​(c); Console.Writel_ine ("(0)", a); ))

Ultimul operator unar pe care l-am menționat este T (x). Aceasta este o variație a operatorului de paranteză care vă permite să turnați de la un tip la altul. Deoarece poate fi supraîncărcat prin crearea unei transformări personalizate, o vom discuta în Capitolul 13.

Operatori de atribuire compusă

Operator de atribuire compusă - este o combinație a operatorului binar și a operatorului de atribuire (=). Sintaxa pentru acești operatori este următoarea:

refren = y

Unde op - acesta este operatorul. Rețineți că atunci când faceți acest lucru, valoarea stângă (valoare l) neinlocuit cu drept (valoarea r), o declarație compusă are același efect ca:

x = x op la

și lvaloarea folosit ca bază pentru rezultatul operației.

Observați că am spus „are același efect”. Compilatorul nu traduce o expresie ca x + = 5 in NS= x + 5, el actioneaza logic. O atenție deosebită trebuie acordată cazurilor în care lvaloarea este o metoda. Luați în considerare codul:

folosind System;

clasa CompoundAssignmentlApp (

elemente lnt protejate;

public int GetArrayElementO

{

elemente de returnare;

}

CompoundAssignment1App () (

elemente = new int;

elemente = 42;

}

public static void Principal () (

CompoundAssignmentlApp app = nou CompoundAsslgnment1App ();

Console.WrlteLine ("(0>", app.GetArrayElement ());

app.GetArrayElement () = app.GetArrayElement () + 5; Console.WriteLine ("(0)", app.GetArrayElement ()); ). )

Observați linia evidențiată - apel de metodă Compound-AssignmentlApp.GetArrayElementși apoi schimbând primul element - aici am folosit sintaxa:

x = x op la

Acesta este modul în care va fi generat codul MSIL: // Tehnica ineficientă: x = x op y.

Metoda public hldebyslg static void Main () 11 gestionat

Polnt de intrare

// Mărimea codului 79 (Ox4f) .maxstack 4

Localnici (clasa CompoundAssignmentlApp V_0)

IL_0000: instanță newobj void CompoundAssignmentlApp ::. Ctor () IL_0005: stloc.O IL_0006: Idstr "(OG IL_OOOb: ldloc.0 ILJJOOc: apel instanță int32

CompoundAssignmentlApp :: GetArrayElementO

IL_0011: ldc.14.0

IL_0012: Idelema ["mscorlib"] System.Int32

IL_0017: caseta [1 mscorlib "] System.Int32

IL_001c: call void ["mscorlib 1] System.Console :: WriteLine (clasa System.String, clasa System.Object)

IL_0021: ldloc.0

IL_0022: apelați instanța int32 CompoundAssignmentlApp :: GetArrayElementO

IL_0027: Idc.i4.0

IL_0028: ldloc.0

IL_0029: apelați instanța int32 CompoundAssignmentlApp:: GetArrayElementO

IL_002e: Idc.i4.0

IL_002f: ldelem.14

IL_0030: ldc.14.5

IL_0031: adaugă

IL_0032: stelem.14

IL_0033: Idstr „(0)”

IL_0038: ldloc.0

IL_0039: apel

instanță int32 CompoundAssignmentlApp :: GetArrayElement () IL_003e: ldc.14.0

IL_003f: Idelema ["mscorlib"] Systera.Int32 IL_0044: caseta ["msoorlib"] System.Int32 IL_0049: call void ["mscorlib"] System.Console :: WriteLine

(clasa System.String, clasa System.Object) IL_004e: ret

) // sfârșitul metodei „CompoundAssignmentlApp :: Main”!

Uită-te la liniile întrețesute: metoda CompoundAssignmentlApp.Get-ArrayElement este de fapt chemat de două ori! Acest lucru este cel puțin ineficient și, posibil, dăunător, în funcție de ce altceva face metoda.

Acum să ne uităm la un alt cod care utilizează sintaxa operatorului de atribuire compusă:

folosind System;

clasa CompoundAssignment2App (

elementele int protejate;

public int GetArrayElementO

elemente de returnare;

}

CompoundAssignment2App () (

elemente = new int;

elemente = 42;

}

public static void Principal () (

CompoundAssignment2App app = nou CompoundAssignment2App ();

Console.WriteLine ("(0)", app.GetArrayElement ());

app.GetArrayElement () + = 5; Console.WriteLine ("(0)", app.GetArrayElement ()); ))

Utilizarea operatorului de atribuire compusă va avea ca rezultat un cod MSIL mult mai eficient:

// Tehnica mai eficientă: x op = y.

Metoda public hidebysig static void Main () il gestionat

\ {

\ .punct de intrare

I // Mărimea codului 76 (Ox4c) \ .maxstack 4

Locali (clasa CompoundAssignmentlApp V_0, int32 V_1) \ IL_0000: instanță newobj void CompoundAssignmentlApp ::. Ctor () \ IL_0005: stloc.O 1 IL_0006: Idstr „(0)” 1 IL_OOOb: ldloc.0 IL_OOOc: apelează instanța int32

CompoundAssignmentlApp :: GetArrayElementO IL_0011: Idc.i4.0

IL_0012: Idelema [mscorlib "] System.Int32 IL_0017: caseta [> mscorlib -] System.Int32 lL_001c: call void [" mscorlib "] System.Console :: WriteLine

(clasa System.String, clasa System.Object) IL_0021: ldloc.0 IL_0022: apel instanță int32

CompoundAssignmentlApp :: GetArrayElement ()

IL_0027: dup

IL_0028: stloc.1

IL_0029: Idc.i4.0

IL_002a: ldloc.1

IL_002b: Idc.i4.0

IL_002c: ldelem.14

IL_002d: ldc.14.5

IL_002e: adăugați

IL_002f: stelem.14

IL_0030: Idstr „(0)”

IL_0035: ldloc.0

IL_0036: apelați instanța int32

CompoundAssignmentlApp :: GetArrayElementO IL_003b: Idc.i4.0

IL_003c: Idelema ["mscorlib"] System.Int32

IL_0041: caseta [mscorlib "] System.Int32

IL_0046: call void ["mscorlib"] System.Console :: WriteLine

(clasa System.String, clasa System.Object)

IL_004b: ret) // sfârșitul metodei „CompoundAssignmentlApp :: Main”

Puteți vedea comanda MSIL utilizată dup. Se dublează elementul de sus pe stivă, creând astfel o copie a valorii returnate de metodă CompoundAssignmentlApp.Get Array Element. eu

Aceasta arată că deși în esență x + = y echivalentă cu x = x + y, Codul MSIL este diferit în ambele cazuri. Această distincție ar trebui să vă facă să vă întrebați ce sintaxă să utilizați în fiecare caz. O regulă generală și recomandarea mea este să folosiți operatori de atribuire compuși ori de câte ori și oriunde este posibil.

Operatori de creștere și decreștere

Introduși în C și portați în C++ și Java, operatorii de creștere și decrementare vă permit să exprimați concis că doriți să creșteți sau să micșorați o valoare numerică cu 1. Adică, / ++ este echivalent cu adăugarea a 1 la valoarea curentă de / ".

Prezența celor două forme ale operatorilor de creștere și decrementare este uneori confuză. Prefixși postfix tipurile acestor operatori diferă în momentul în care se modifică valoarea. În versiunea de prefix a operatorilor de creștere și decrementare (++ ai - a respectiv) mai întâi se realizează operaţia şi apoi se creează valoarea. În versiunea postfix (a ++și A-) mai întâi se creează valoarea și apoi se realizează operația. Să luăm în considerare un exemplu:

folosind System;

clasa IncDecApp (

public static void Foo (int j)

{

Console.WriteLine ("IncDecApp.Foo j = (0)", j);

>

public static void Principal () (

int i = 1;

Console.WriteLineC „flo apelează la Foo (i ++) = (0)”, i);

Console.WriteLine ("După apelarea lui Foo (i ++) = (0)", i);

Console.WriteLine ("\ n");

\ Console.WriteLineC „flo apelează la Foo (++ i) = (0)”, i);

\ Foo (++ l);

\ Console.WrlteLine ("După apelarea lui Foo (++ i) = (0)", i);

l Rezultatul va arăta astfel:

Înainte de a apela Foo (i ++) = 1

IncDecApp.Foo j = 1

După apelarea lui Foo (i ++) = 2

Înainte de a apela Foo (-n-i) = 2

IncDecApp.Foo j = 3

După apelarea lui Foo (++ i) = 3

Diferența este cand se creează valoarea și se modifică operandul. Când suni Foo (i ++) valoarea / „este transmisă (neschimbată) metodei Fooși după metoda return / este incrementată cu 1. Uitați-vă la codul MSIL de mai sus: comanda adăuga executat după ce valoarea este împinsă în stivă.

IL.0013: ldloc.0

IL.0014: dup

IL_0015: Idc.i4.1

IL_0016: adaugă

IL_0017: stloc.O

IL_0018: call void IncDecApp :: Foo (int32)

Acum să ne uităm la forma operatorului de prefix folosită în apel Foo (++ a).În acest caz, codul MSIL va fi diferit. În acest caz, comanda adăuga efectuat inainte de modul în care valoarea este împinsă în stivă pentru invocarea ulterioară a metodei Foo.

IL.0049: ldloc.0

IL_004a: Idc.i4.1

IL_004b: adăugați

IL_004c: dup

IL_004d: stloc.O

IL_004e: call void IncDecApp :: Foo (int32)

Operatori relaționali

Majoritatea operatorilor returnează valori numerice. În ceea ce privește operatorii relaționali, aceștia generează un rezultat boolean. În loc / în loc să efectueze operații matematice asupra unui set de operanzi, / operatorii relaționali analizează relația dintre operanzi și întoarcere sens Adevărat, dacă raportul este adevărat, fals - dacă / este fals.

Operatori de comparare

La operatorii relaționali numiți operatori de comparație, referiți) - este „mai puțin” (<), «меньше или равно» (<=), «больше» (>), Mai mare decât sau Egal (> =), Egal (==) și Nu este egal (! =). Aplicarea acestor operatori la numere este de înțeles, dar atunci când este folosit cu obiecte, implementarea lor nu este atât de evidentă. Iată un exemplu:

folosind System;

clasa NumericTest (

public NumericTest (int 1)

{

aceasta.i = i;

>

protejat int 1; )

clasa RelationalOpslApp (

public static void Principal () (

NumericTest testl = new NumericTest (42); NumericTest test2 = nou NumericTest (42);

Console.WriteLine ("(0)", testl == test2); >)

Dacă ești un programator Java, știi ce se va întâmpla aici. Cu toate acestea, programatorii C++ vor fi cel mai probabil surprinși să vadă rezultatul fals. Permiteți-mi să vă reamintesc că atunci când creați o instanță a unui obiect, obțineți un link către acesta. Aceasta înseamnă că atunci când întâlnește un operator de relație care compară două obiecte, compilatorul C # compară nu conținutul obiectelor, ci adresele acestora. Pentru a înțelege mai bine acest lucru, luați în considerare codul MSIL:

\ .method public hldebysig static void MainQ il gestionat

\ .punct de intrare

\ // Dimensiunea codului 39 (0x27)

1 .maxstack 3

Localnici (clasa NumericTest V_0, \ clasa NumericTest V_1, 1 bool V_2)

ILJJOOO: Idc.i4.s 42

1L_0002: instanță newobj void NumericTest ::. Ctor (int32)

IL_0007: stloc.O

IL_0008: Idc.i4.s 42

IL_OOOa: instanță newobj void NumericTest ::. Ctor (int32)

IL_OOOf: stloc.1

IL_0010: Idstr „(0)”

IL_0015: ldloc.0

IL_0016: ldloc.1

IL_0017: eeq

IL_0019: stloc.2

IL_001a: Idloca.s V_2

IL_001c: caseta ["mscorlib"] System.Boolean

IL_0021: call void ["mscorlib"] System.Console :: WriteLine

(clasa System.String, clasa System.Object)

IL_0026: ret) // sfârșitul metodei „RelationalOpslApp :: Main”.

Uită-te la linie .localnici. Compilatorul indică faptul că metoda Principal trei variabile locale. Primele două sunt obiecte NumericTest, o a treia este o variabilă booleană. Acum să trecem la linii IL_0002și IL_0007. Aici este instanțiat obiectul testl,și leagă la el cu stloc este stocat în prima variabilă locală. Este important, totuși, ca MSIL să păstreze adresa obiectului nou creat. Apoi în rânduri IL_OOOași IL_OOOf vedeți codurile MSIL pentru crearea obiectelor testul2și stocarea referinței returnate într-o a doua variabilă locală. În fine, în rânduri 1LJ) 015și IL_0016 variabilele locale sunt împinse în stivă de către comandă Idloc, iar în linie IL_0017 comanda gri compară cele două valori din partea de sus a stivei (adică referințe la obiecte testlși testl). Valoarea returnată este stocată în a treia variabilă locală și apoi afișată prin metodă Sistem.Consola. WriteLine.

Dar cum compari membrii a două obiecte? Răspunsul este să folosiți clasa de bază implicită a tuturor obiectelor .NET Framework. Și anume, în aceste scopuri, clasa Sistem.Obiect exista o metoda Egal. De exemplu, următorul cod compară conținutul obiectelor și ieșirilor, așa cum v-ați aștepta, adevărat: eu

folosind System; /

clasa RelationalOps2App

(/ public static void Principal () (

Console.WriteLine ("(0)", testl.Equals (test2));

Exemplul RelationalOpslApp folosește o clasă „de casă”. (Nu-mericTest) șiîn al doilea exemplu - clasa .NET (Zecimal). Ideea este că metoda Sistem.Obiect.Egal trebuie să fie suprascris pentru a face o comparație cu membrii reali. De aici metoda Egal cu clasa Nume-ricTest nu va funcționa pentru că nu am suprascris metoda. Și aici este clasa Zecimal anulează metoda pe care o moștenește egal,și în acest caz totul va funcționa.

Un alt mod de a compara obiecte este utilizarea supraîncărcarea operatorului(supraîncărcarea operatorului). Supraîncărcarea operatorului definește operațiunile efectuate asupra obiectelor de un anumit tip. De exemplu, pentru obiecte şir operatorul + nu efectuează adunări, ci mai degrabă concatenează șiruri. Vom acoperi supraîncărcarea operatorului în capitolul 13.

Operatori simpli de atribuire

Este apelată valoarea din partea stângă a operatorului de atribuire lvaloare,și pe partea dreaptă - rvalue. La fel de rvalue poate fi orice constantă, variabilă, număr sau expresie al cărei rezultat este compatibil lvaloarea.Între timp lvaloarea trebuie să fie o variabilă de un anumit tip. Ideea este că valoarea este copiată din partea dreaptă spre stânga. Astfel, pentru noua valoare trebuie alocat un spațiu de adresă fizică. De exemplu, puteți scrie / „= 4, deoarece există loc pentru / în memorie - fie pe stivă, fie pe heap - în funcție de tipul de /. Și aici este operatorul 4 = 1 nu poate fi executat deoarece 4 este o valoare, nu o variabilă al cărei conținut în memorie poate fi modificat. Apropo, voi observa că în C # ca lvaloarea poate fi o variabilă, o proprietate sau un indexator. Pentru mai multe informații despre proprietăți și indexatori, vezi Capitolul 7. În acest capitol, folosesc variabile pentru simplitate. În timp ce atribuirea valorilor numerice este suficient de clară, obiectele sunt mai complicate. Permiteți-mi să vă reamintesc că atunci când aveți de-a face cu obiecte, manipulați elemente care nu sunt stivuite, care sunt ușor de copiat și mutat. În cazul obiectelor, de fapt aveți doar referințe la unele dintre entitățile pentru care memoria este alocată dinamic. Prin urmare, atunci când încercați să atribuiți un obiect (sau orice tip de referință) unei variabile, nu datele sunt copiate, cum este cazul tipurilor dimensionale, ci referințele.

Să presupunem că aveți două obiecte: testlși testul2. Daca specificati testl= test2, testl nu va fi o copie testul2. Se vor potrivi! Un obiect testl indică aceeași memorie ca test2,și orice modificări aduse obiectului testl va duce la schimbări testul2. Iată un program care ilustrează acest lucru:

folosind System;

clasa Foo (

public int i; )

clasa RefTestlApp (

public static void MainO (

Foo testl = nou Foo (); testl.i = 1;

Foo test2 = nou Foo (); test2.i = 2;

Console.WriteLine („Înainte de atribuirea obiectelor”); Console.WriteLine ("test1.i = (0>", testl.i); Console.WriteLine ("test2.i = (0)", test2.i); Console.WriteLine ("\ n");

testl = test2;

Console.Writel_ine ("După atribuirea obiectelor");

Console.WriteLine ("test1.i = (0)", testl.i); Console.WriteLine ("test2.i = (0)", test2.i); Console.WriteLine ("\ n");

testl.i = 42; ; "

Console.WriteLine ("După schimbarea doar membrului TEST1"); Console.WriteLine ("test1.i = (0)", testl.i); Console.WriteLine ("test2.i = (0)", test2.i); Console.WriteLine ("\ n"); ))

Rulând acest cod, veți vedea:

Înainte de a atribui un obiect

test1.i = 1

test2.i = 2

După atribuirea unui obiect

testt.i = 2

test2.i = 2

După schimbarea doar membrului TEST1

test1.i = 42

test2.i = 42

Să vedem ce se întâmplă în fiecare etapă a acestui exemplu. Foo - este o clasă k simplă cu un singur membru, /. În metoda Main, sunt create două instanțe ale acestei clase: testlși testul2- și membrii acestora i sunt setate la 1 și, respectiv, 2. Aceste valori sunt apoi afișate, așa cum era de așteptat testl.i este egal cu 1, a test2.i- 2. Și aici începe distracția! Pe linia următoare la obiect testl atribuit testul2. Cititorii Java știu ce urmează. Cu toate acestea, majoritatea programatorilor C++ se vor aștepta la un membru/obiect testl este acum egal cu un membru al obiectului testul2(presupunând că atunci când o astfel de aplicație este compilată, va fi executat un fel de operator care să copieze membrii obiectului). Rezultatul afișat pare să confirme acest lucru. Cu toate acestea, în realitate, legătura dintre obiecte este acum mult mai profundă. Atribuiți valoarea 42 membrului testl.iși imprimați din nou rezultatul. ȘI?! Când obiectul se schimbă testl schimbată şi testZ Acest lucru s-a întâmplat datorită faptului că obiectul testl nu mai. După ce l-a atribuit testul2 un obiect testl se pierde deoarece nu mai este referit de aplicație și, ca urmare, este „curățat” de către colectorul de gunoi (GC). Acum testlși testul2 indică aceeași memorie pe grămadă. Prin urmare, atunci când o variabilă se schimbă, utilizatorul va vedea și cealaltă modificare.

Observați că ultimele două rânduri sunt tipărite: deși doar valoarea sa schimbat în cod testl.i, sens test2.i schimbat de asemenea. Încă o dată, ambele variabile indică acum aceeași locație de memorie - acesta este comportamentul la care se aștepta programatorii Java. Cu toate acestea, acest lucru nu satisface deloc așteptările dezvoltatorilor C ++, deoarece în acest limbaj se realizează exact copierea obiectelor: fiecare variabilă are propria sa copie unică a membrilor săi, iar modificările unui obiect nu îl afectează pe celălalt. Deoarece aceasta este cheia pentru înțelegerea modului în care funcționează obiectele în C #, să ne digresăm puțin și să vedem ce se întâmplă când treceți un obiect unei metode:

folosind System;

clasa Foo (

public int i; )

clasa RefTest2App (

public void ChangeValue (Foo f)

{

f.i = 42;

}

public static void Principal () (

Aplicația RefTest2App = noua aplicație RefTest2App ();

Foo test = nou Foo (); test.i = 6;

Console.WriteLine („Înainte de a apela metoda”); Console.WriteLine ("test.i = (0)", test.i); Console.WriteLine ("\ n");

app.ChangeValue (test);

Console.WriteLine ("După apelarea metodei"); Console.WriteLine ("test.i = (0)", test.i); Console.WriteLine ("\ n"); >)

În majoritatea limbilor, altele decât Java, acest cod va copia obiectul creat testa in stiva de metode locale RefTest2App.ChangeValue.În acest caz, obiectul Test, creat în metodă Principal, nu va vedea niciodată modificări la obiectul / făcute în metodă SchimbațiValoarea. Cu toate acestea, repet încă o dată că metoda Principal transmite o referință la un obiect alocat în heap Test. Când metoda Schimbați valoarea manipulează variabila locală //, manipulează și obiectul direct Test metodă Principal.

Să rezumam

Cheia oricărui limbaj de programare este modul în care sunt efectuate sarcinile, operațiunile matematice, logice și relaționale - tot ceea ce este necesar pentru a rula aplicații din lumea reală. În cod, aceste operații sunt reprezentate de operatori. Factorii care afectează execuția operatorilor includ precedența și asociativitatea (dreapta și stânga) operatorilor. Setul puternic de operatori predefiniți în C # poate fi extins cu implementări definite de utilizator, despre care vom vorbi în Capitolul 13.

Top articole similare