Poglavje 3
Operatorji in izrazi

V tem poglavju pregledamo pomen in lastnosti operatorjev in izrazov jezika java. Poleg aritmetičnih, logičnih in relacijskih operatorjev spoznamo tudi bitne operatorje in operator ?. Na koncu poglavja spoznamo, v kakšnem odnosu so ti operatorji med sabo, kateri med njimi ima prednost in v kakšnem vrstnem redu se izvaja izračun vrednosti izrazov, ki vsebujejo te operatorje.

3.1 Aritmetični operatorji

Java pozna 7 aritmetičnih operatorjev, ki jih uporabljamo za izvajanje osnovnih aritmetičnih operacij: seštevanje, odštevanje, množenje in deljenje. Poleg tega java loči med celoštevilskim in realnim deljenjem: pri prvem se izračuna celoštevilski količnik in ostanek, pri drugem pa realni količnik. Tip deljenja je določen s tipom operandov (med celoštevilskimi operandi se izvaja celoštevilsko deljenje, med realnimi pa realno).

Operatorji se delijo na unarne in binarne. Prvi delujejo nad enim operandom (primer: -1; - je unarni operator negacije, 1 operand), drugi nad dvema (primer: 3 + 4; + je operator seštevanje, 3 in 4 pa operanda).

3.2 Relacijski in logični operatorji

Relacijske in logične operatorje uporabljamo za sestavljanje logičnih izrazov. Java pozna 6 relacijskih operatorjev

in tri logične

Aritmetični operatorji imajo prednost pred relacijskimi, relacijski pa pred logičnimi. Zato, na primer, i < a-1 pomeni isto kot i < (a-1) in a < 5 && b > 3 isto kot (a < 5) && (b > 3).

Logičen izraz lahko običajno sestavimo na več načinov. Primer: če bi radi preverili, ali spremenljivka x zavzema vrednost iz intervala [0..10], to storimo (vsaj) na dva načina:

 

if ((x >= 0) && (x <=10)) // if (!((x < 0) || (x > 10)))

Logični izrazi se preverjajo od leve proti desni. Izračun vrednosti logičnih izrazov se konča takoj, ko je znana končna vrednost. Primer: (A1 && .. && An) ima končno vrednost false takoj, ko se najde prvi izraz (od leve proti desni) Ai z vrednostjo false.

3.3 Operatorji ++, --, +=, -=, *=, /= in %=

Operatorja ++ in -- se uporabljata za povečanje in zmanjšanje vrednosti spremenljivk. Namesto njiju lahko vedno uporabimo običajna operatorja za seštevanje in odštevanje (+ in -), toda uporaba operatorjev ++ in -- poenostavi in predvsem skrajša izvorno kodo programa. Operatorja ++ in -- lahko uporabljamo v prefiksni  

             ... isto kot ... 
  ++i;                          i = i + 1; 
  --i;                          i = i - 1; 
 
  x = ++n;                      n = n + 1; x = n; 
  y = --i;                      i = i - 1; y = i; 
 
  t[++i]=a;                     i = i + 1; t[i] = a;

ali postfiksni obliki

 

             ... isto kot ... 
  i++;                          i = i + 1; 
  i--;                          i = i - 1; 
 
  x = n++;                      x = n; n = n + 1; 
  y = i--;                      y = i; i = i - 1; 
 
  t[i++]=a;                     t[i] = a;  i = i + 1;

Razlika med prefiksno in postfiksno obliko operatorjev ++ in -- je v tem, da prva operand najprej spremeni, nato uporabi, druga pa ga najprej uporabi, nato spremeni. To nazorno vidimo v zadnjih dveh vrsticah v zgornjih primerih. Namreč t[++i]=a; pomeni: najprej povečaj i, nato spremenjeni i uporabi kot indeks v tabeli. Nasprotno t[i++]=a; pomeni: kot indeks v tabeli uporabi nespremenjeni i, šele nato povečaj vrednost števca i.

Izvorno kodo lahko skrajšamo tudi z uporabo operatorjev +=, -=, /=, *= in %=

 

             ... isto kot ... 
  i+=5;                         i = i + 5; 
  i-=2;                         i = i - 2; 
  i/=2;                         i = i / 2; 
  i*=2;                         i = i * 2; 
  i%=2;                         i = i % 2;

3.4 Bitni operatorji

Bitni operatorji delujejo nad biti dvojiških predstavitev operandov. Delovanje bitnih operatorjev lahko opišemo takole: operande najprej pretvorimo v dvojiški sistem, nato izvedemo operacijo nad posameznimi biti in na koncu dobljeni rezultat pretvorimo v desetiški sistem. Primer: pri računanju vrednosti 10 & 3 operanda 10 in 3 zapišemo v dvojiškem sistemu kot 1010 in 0011; operator & (bitni in) pregleda soležne bite in vrne 1 tam, kjer sta postavljena (imata vrednost 1) oba bita, in 0 sicer. V številih 10 in 3 sta hkrati postavljena samo predzadnja bita, zato je rezultat operacije 10 & 3 enak 0010 oziroma 2 po desetiško. Torej 10 & 3 == 2.

Tako razlago bitnih operatorjev (da pred izvedo operacije opravimo pretvorbo v dvojiški sistem), uporabljamo ljudje, ki o številih razmišljamo na desetiški način. Za računalnik je stvar mnogo preprostejša, saj so osnovne predstavitve števil zanj dvojiške. Dvojiške operacije so zato za računalnik “osnovne” operacije. in kot take se tudi zelo hitre, zato je njihova uporaba priporočljiva povsod, kjer je to mogoče.

Java pozna naslednje bitne operatorje:

Primer bitnih operacij (glej po stolpcih)
 01011    01011    01011    ~01011 
&00101   |00101   ^00101 
=00001   =01111   =01110    =10100

Operatorje za pomik bitov pogosto uporabljamo namesto množenja in deljenja. Za pozitivna števila je namreč enkraten pomik bitov v levo (desno) isto kot množenje (deljenje) tega števila z 2, le da se bitna operacija izvede hitreje kot operaciji * in /.

 

a = b << 1;    // isto kot a=b*2 
a = b << 3;    // isto kot a=b*8 
a = b >> 1;    // isto kot a=b/2 
a = b >> 10;   // isto kot a=b/1024

Naloga 3-I. Napiši metodo, ki prešteje in vrne število prižganih (postavljenih) bitov danega števila.  

Nalogo rešimo tako, da preverimo ali je postavljen zadnji (desni) bit podanega števila (n & 1 ima vrednost 1 če je bit postavljen in 0, če ni), nato število pomaknemo v desno (n = n >> 1). Postopek ponavljamo, dokler je število različno od 0.

Rešitev naloge 3-I.: Število bitov v podanem številu operatorji/Biti.java

 

Naloga 3-II. Napiši metodo, ki izračuna BSD kontrolno vsoto podanega niza.  

S pomočjo Googla na internetu hitro najdemo wiki opis za izračun BSD kontrolne vsote, ki se glasi  

int checksum = 0;     /* The checksum mod 2^16. */ 
 
while ((ch = getc (fp)) != EOF) 
  { 
    ... 
    checksum = (checksum >> 1) + ((checksum & 1) << 15); 
    checksum += ch; 
    checksum &= 0xffff;       /* Keep it within bounds. */ 
  }

Gre za preprost postopek, ki (podobno kot naš algoritem v nalogi 2-XIV) vsako črko prišteje prejšnji vsoti. Razlika je le v tem, da BSD checksum pred tem vsoto malo “pofrizira”. S tem preprostim posegom dobimo mnogo bolj zanesljivo metodo za izračun kontrolne vsote. Zgornji postopek z lahkoto pretvorimo v javansko narečje.

Rešitev naloge 3-II.: BSD kontrolna vsota operatorji/BSDchecksum.java

 

Če program poženemo, dobimo

BSD niza abc je 16556 
BSD niza abcd je 8378 
BSD niza bac je 172

Vidimo, da je BSD checksum zelo podobnih nizov zelo različen, kar onemogoča “goljufije”, kakršne smo videli pri našem preprostem algoritmu.

Naloga 3-III. Napiši metodi static String vDvojisko(int x) in static int vDesetisko(String d) za pretvorbo med desetiškim in dvojiškim številskim sistemom. Metodi preveri v main metodi na nekaj primerih.  

Pretvorba dvojiško/desetiško operatorji/Dvojisko.java

 

3.5 Operator ?:

Operator ?: je sintaksni sladkorček, ki nam v določenih primerih omogoča krajši zapis kode. Sladkorček zato, ker bi brez njega čisto dobro živeli, z njim pa živimo še malo bolje. Stranski učinki (gnili zobje) pa se v tem primeru kažejo v manj čitljivi kodi. Operator ?: uporabimo tako, da namesto

 

if (pogoj) 
  rezultat = izraz1; 
 else 
  rezultat = izraz2;

skrajšano zapišemo

 

rezultat = pogoj ? izraz1 : izraz2;

Operator ?: lahko uporabimo tudi pri klicu metod (pri podajanju parametrov), na primer takole:

 

  System.out.printf("%s", x < 37 ? "OK" : "VROCINA!");

Zgornja koda bo v primeru, da ima spremenljivka x vrednost manjšo od 37, izpisala OK, sicer VROCINA!

3.6 Prireditveni stavki

V jeziku java prireditveni stavek vrne vrednost. Primer: vrednost prirejanja

 

 b = a;

je a. To dejstvo uporabimo v večkratnem prirejanju

 

 c = b = a;

ali, na primer, takole:

 

while ((c=fis.read()) != -1) { 
  ... 
}

Razlaga: prirejanje c = fis.read() vrne rezultat (vrednost funkcije fis.read()) in ta rezultat se primerja z -1; pogoj bi sicer lahko napisali tudi brez prirejanja (while (fis.read() != -1)) toda v tem primeru v telesu zanke ne bi vedeli, kateri znak smo prebrali.

3.7 Prioriteta in asociativnost operatorjev

Se bo v (d = a + b * c) najprej izračunal seštevek ali zmnožek?
Odgovor: Najprej se bo izračunal zmnožek, saj ima operator * večjo prioriteto kot operator +.
V kakšnem vrstnem redu se bo izračunal izraz (d = a / 4 / 2)?
Odgovor: ker je operator / levo-asociativen, je zgornji izraz ekvivalenten izrazu d = (a / 4) / 2.

Spodnja tabela prikazuje prioriteto (višje v tabeli pomeni višjo prioriteto) in asociativnost operatorjev. Iz tabele vidimo, na primer, da imajo relacijski operatorji višjo prioriteto od logičnih, zato je pogoj (a < 2 && b > 3) ekvivalenten pogoju ((a < 2) && (b > 3)).

Operatorji Asociativnost


(), [], . L


!, ~, ++, --, +, -, (type), new

D


*, /, %

L


+, -

L


<<, >>, >>> L


<, <=, >=, >,

instanceof

L


==, ! = L


&

L


^

L


|

L


&&

L


||

L


?:

D


=, + =, - =, * =, =, %=, &=, =, | =, <<=, >>=, >>>=D



Tabela: Prioriteta in asociativnost operatorjev v javi