3. Operatory i wyrażenia logiczne
Do zapisywania złożonych warunków służą (m.in) warunkowe operatory logiczne:
- jednoargumentowy operator negacji logicznej oznaczany przez ! ("nie"),
- dwuargumentowy operator koniunkcji oznaczany przez && ("i"),
- dwuargumentowy operator alternatywy oznaczany przez || ("albo").
Przypuścmy, że chcemy sprawdzić czy zachodzi warunek: wielkość n znajduje
się w przedziale [1, 10] - to znaczy, czy n jest większe lub równe 1 i jednocześnie
mniejsze lub rowne 10.
Jak już wiemy, zapis: if (1 <= n <= 10) będzie błędny (bo operatory
relacyjne są stosowane tylko wobec typów numerycznych, a wyrażenie 1 <=
n ma wynik typu boolean).
Nie znając operatorów logicznych i wiedząc, że w instrukcji if możemy umieścić
inną instrukcję if, warunek moglibyśmy sprawdzić za pomocą takiego zapisu:
if (n >= 1)
if (n <= 10) ... ;
Nie jest to jednak ani elegancka, ani zbyt czytelna forma.
Na szczęście mamy operator koniunkcji logicznej,
Chodzi nam o to czy równocześnie spelnione są dwa warunki
n >= 1 I n <= 10.
W Javie zapisujemy to jako:
n >= 1 && n <= 10
i stosujemy np. w instrukcji if:
if (n >= 1 && n <= 10) ...;
Podobnie możemy chcieć sprawdzić czy wartość n znajduje się poza przedziałem [1, 10]. Aby tak było n musi być mniejsze od 1 lub większe od 10.
Do zapisu takiego warunku stosuje się operator logicznej alternatywy ||:
if (n < 1 || n > 10) ..
Trzeci operator logiczny - negacji - służy do zaprzeczania warunkom.
Na przykład, jeżeli chcemy podjąc jakieś działania, jeżeli łańcuch znakowy txt nie jest napisem "Ala", to piszemy:
if (!txt.equals("Ala")) ... ;
Argumentami operatorów logicznych mogą być tylko wyrażenia typu boolean, zaś rezultatem jest wartość typu boolean, która może być równa true lub false, zgodnie z następującą tablicą:
Warunkowe operatory logiczne
| a
| b
| a && b
| a || b
| !a
| true
| true
| true
| true
| false
| false
| true
| false
| true
| true
| true
| false
| false
| true
|
| false
| false
| false
| false
|
|
Operator && daje wartość true tylko wtedy, gdy oba jego argumenty są wartościami true, a wartość false zawsze, gdy którykolwiek z argumentów jest false.
Operator || daje wartość true zawsze, gdy którykolwiek z jego argumentów jest true i wartość false tylko wtedy, gdy oba argumenty są false
Operatory logiczne mają niższy priorytet od operatorów relacyjnych i równości-nierówności,
zatem bez użycia dodatkowych nawiasów możemy pisać np:
if ( a == 1 && b > 3) c = a + b;
uzyskując efekt, o który nam chodzi (tu: wyliczenie c, gdy a równe 1 i b > 3).
Uwaga: operator negacji logicznej ma wyższy priorytet niż inne operatory
logiczne, wobec tego należy używać nawiasów dla zapewnienia właściwej kolejności obliczania wartości wyrażeń
Sprawne posługiwanie się warunkami logicznymi wymaga podstawowej
wiedzy z zakresu logiki matematycznej. Warto więc sięgnąć po jakiś prosty,
początkowy opis rachunku zdań logicznych i przeczytać z niego kilka pierwszych
stron.
Nota bene, zadziwiająco często mylone są w programowaniu operacje koniunkcji i alternatywy
logicznej. Zawsze więc - wybierając operację do zastosowania - zastanówmy
się troszkę dłużej nad tym czy ma to być koniukcja czy alernatywa
Np. jeżeli
chcemy wykonać jakieś czynności jeżeli nie zachodzi warunek:
"łańcuch znakowy nie jest napisem "Ala" i - jednocześnie - zmienna lata ma
wartość większą od 1", to nie wolno nam napisać:
if (! txt.equals("Ala") && lata > 1) ...
bo to oznaczałoby konieczność równoczesnego spełnienia dwóch warunków:
- napis txt nie jest ala,
- i lata są większe od 1
(a przecież nie o to nam chodziło)
Piszemy za to:
if (!( txt.equals("Ala") && lata > 1)) ...
Użycie dodatkowych nawiasów zmienia kolejność opracowania wyrażenia i uzyskujemy pożądany efekt.
Omawiane dotąd operatory nazywają się warunkowymi operatorami logicznymi, bowiem
obliczanie wartości wyrażeń logicznych, konstruowanych za ich pomocą ma bardzo ważną
właściwość: obliczane są one od lewej do prawej (wiązania operatorów logicznych
- oprócz operatora negacji - są lewostronne), ale proces obliczeń kończy
się już w momencie gdy tylko wiadomo jaki będzie rezultat całego wyrażenia.
Oznacza to, że wyrażenie nie musi być wcale opracowywane do końca; wystarczy,
że jego część jednoznacznie wskazuje wynik. Na przykład w wyrażeniu:
a && (b > 0 || c == 1)
jeśli a jest rowne false, to wyrażenie w nawiasach nie będzie obliczone,
bowiem wynik całego wyrażenia nie zależy już od tego co jest w nawiasach
(przy a = false zawsze będzie false).
Ma to ogromnie ważne konsekwencje, bowiem jeśli oprzemy logike programu na
efektach ubocznych generowanych przy obliczaniu wyrażeń, które - właśnie
ze względu na tę właściwość operatorów logicznych - mogą w ogóle nie podlegać
opracowaniu, to może nas spotkać bardzo przykra niespodzianka.
Zobaczmy to na przykładzie poniższego programu.
Programista, który go napisał chciał osiagnąć następujący efekt: zapytać
w dialogach wejściowych o nazwisko i imię użytkownika, jeśli podano obie
informacje - wyprowadzić połączone nazwisko i imię, jeśli zaś zabrakło ktorejś
z nich - wyprowadzić napis "Niepełna informacja"; następnie pokazać dokładnie
co jest nazwiskiem (może być null) a co imieniem (też może być null, jeśli
nie wprowadzone). Pokusa zwięzłego napisania kodu sprawiła, że nasz programista
sięgnął po połączenie warunków za pomocą operatora &&: jeżeli wprowadzono
nazwisko i wprowadzono imię to txt = nazwisko + imie.
import javax.swing.*;
public class EfUb {
public static void main(String[] args) {
String nazwisko;
String imie = null;
String txt;
if ((nazwisko = JOptionPane.showInputDialog("Podaj nazwisko")) != null
&& (imie = JOptionPane.showInputDialog("Podaj imie")) != null
)
txt = nazwisko + " " + imie;
else txt = "Niepełna informacja";
System.out.println(txt);
System.out.println("Imie :" + imie);
System.out.println("Nazwisko :" + nazwisko);
System.exit(0);
}
}
Jednak raczej wbrew intencjom programisty w tym programie może nie dojść do zapytania o imię (bowiem
wartość pierwszego składnika wyrażenia połączonego koniunkcją &&
może być false - jeśli przy pytaniu o nazwisko użytkownik anulował dialog).
W Javie są również bezwarunkowe operatory logiczne (zwane czasem po prostu operatorami logicznymi):
- & koniunkcja
- | alternatywa
- ^ "wyłączające albo"
- ~ negacja
Bezwarunkowe operatory logicznej koniunkcji (&) i alternatywy ( |) mają
te same właściwości co operatory && i || z jednym wyjątkiem: oba
wyrażenia-argumenty operatorów są zawsze opracowywane (dlatego mówimy, że
operatory są bezwarunkowe).
Dodatkowo, wśród logicznych operatorów bezwarunkowych, zdefiniowano
operator "wykluczające albo" (znak ^), który daje wynik true gdy wartości obu argumentów operatora są różne (czyli true i false lub false i true) oraz false w każdym innym przypadku.
Operatory logiczne (bezwarunkowe) są stosowane wyłącznie wobec argumentow typu boolean.
Niestety, te same symbole operatorów ( &, |, ^, ~) sa również używane
w operatorach bitowych, stosowanych wobec liczb całkowitych (i umożliwiających
operowanie na bitach liczb całkowitych), co powoduje trochę zamieszania.
Problemem są także błędy: np. często początkujący (i nie tylko) programiści
zamiast && piszą &, a zamiast || - |. Kompilator nie wykryje
takich błędów, a efekt opracowywania takich wyrażeń logicznych może być inny
od spodziewanego przez programistę.
Generalnie więc:
nie należy mieszać w wyrażeniach użycia operatorów
warunkowych i bezwarunkowych (ze względu na inny sposób działania oraz inne
priorytety (por. tablicę priorytetów operatorów)).
Dobrą regułą "na początek" jest:
- stosować wyłącznie operatory warunkowe,
- a przy tym nie zakładać, że wyrażenia logiczne będą opracowywane w sposób warunkowy (!)
|