Java използва всичките оператори за управление на хода на програмата известни от C, така че ако сте програмирали на C или C++ повечето от това, което ще видите, ще е същото. Повечето процедурни езици имат същите управляващи оператори и често се припокриват едни с други в това отношение. В Java ключовите думи включват if-else, while, do-while, for и switch за избор. Java не поддържа, обаче, много злепоставяното goto (който оператор продължава да бъде най-бързият начин за решаване на някои проблеми). Все още може да се правят подобни на goto скокове, но са по-ограничени в сравнение с него.
true и false
Всичките оператори за условен преход използват лъжливостта или истинността за определяне пътя по който ще мине изпълнението. Пример за условен израз е A == B. Той използва условния оператор == за да види дали стойността на A е равна на стойността на B. Изразът връща true или false. Всеки от операторите за отношения които видяхте в тази глава може да бъде използван за определяне хода на изпълнението. Забележете че Java не позволява използването на число за boolean, нищо че това е позволено в C и C++ (където истината е неравно на нула и лъжата е равно на нула). Ако искате да използвате не-boolean в boolean проверка като if(a), трябва първо да превърнете в boolean стойност използвайки условен израз като if(a != 0).
if-else
if-else е може би най-основният начин да се управлява хода на програмата. еlse-то не е задължително, така че може да използвате if в две форми:
if(Булев израз)
оператор
или
if(Булев израз)
оператор
else
оператор
Условието трябва да дава Boolean резултат. Оператор значи или прост оператор след който има точка и запетая или съставен, който е група от прости оператори затворени в скоби. Винаги когато се използва думата “оператор” се предполага, че операторът може да е прост или съставен.
Като пример за if-else ето test( ) метод който ще познае дали познатото число е по-малко, равно или по-голямо от истинското:
static int test(int testval) {
int result = 0;
if(testval > target)
result = -1;
else if(testval < target)
result = +1;
else
result = 0; // match
return result;
}
Прието е да се прави отместване на тялото на условния израз за да може читателят да вижда къде той започва и свършва.
return
Ключовата дума return има две предназначения: определя каква стойност методът ще връща (ако типът на връщане не е void) и незабавно връща въпросната стойност. test( ) методът по-горе може да бъде пренаписан за да се ползва от предимствата:
static int test2(int testval) {
if(testval > target)
return -1;
if(testval < target)
return +1;
return 0; // match
}
Няма нужда от else понеже методът няма да продължи след изпълнението на return.
Итерация
while, do-while и for управляват цикленето и понякога се наричат оператори за итерация. Оператор rсе повтаря докато управляващия Булев-израз не получи стойност лъжа. Формата на while цикъл е
while(Булев-израз)
оператор
Булев-израз се изчислява преди първото изпълнение и при всяка итерация на оператор.
Ето пример който генерира случайни числа докато се удовлетвори конкретно условие:
//: c03:WhileTest.java
// Demonstrates the while loop
public class WhileTest {
public static void main(String[] args) {
double r = 0;
while(r < 0.99d) {
r = Math.random();
System.out.println(r);
}
}
} ///:~
Използва се static методът random( ) в Math библиотеката, който генерира double стойност между 0 и 1. (включва 0, но не 1.) Условният израз за while казва “продължавай този цикъл докато стойността стане 0.99 или по-голяма.” Всеки път когато стартирате тази програма ще получавате списък стойности който има различна дължина.
do-while
Формата за do-while е
do
statement
while(Boolean-expression);
Единствената разлика между while и do-while че операторът за do-while винаги се използва най-малко един път, даже ако изразът е "лъжа" от първия път. За while, ако условието е лъжа от първия път операторът никога не се изпълнява. На практика do-while по-малко се използва от while.
for
for прави инициализация преди първата итерация. След това проверява условието и, в края на всяка итерация, някаква форма на “стъпки.” Формата за for цикъл е:
for(initialization; Boolean-expression; step)
statement
Кой да е от изразите initialization, Boolean-expression или step може да е празен (т.е. да го няма - б.пр.). Изразът се проверява преди всяка итерация и щом стане false изпълнението продължава със следващия for оператор. В края на всеки цикъл се изпълнява step.
for циклите обикновено се използват за “броящи” задачи:
//: c03:ListCharacters.java
// Demonstrates "for" loop by listing
// all the ASCII characters.
public class ListCharacters {
public static void main(String[] args) {
for( char c = 0; c < 128; c++)
if (c != 26 ) // ANSI Clear screen
System.out.println(
"value: " + (int)c +
" character: " + c);
}
} ///:~
Забележете че променливата c е определена в точката, където е използвана, вътре в управляващия израз на for цикъла, а не в началото на блока означен с отваряща кръгла скоба. Обхватът на c е изразът управляван от for.
Традиционните процедурни езици като C изискват всички променливи да бъдат декларирани в началото на блок така че когато компилаторът генерира блок да може да алокира място за променливите. В Java и C++ може да правите декларациите си навсякъде в блока, дефинирайки променливите когато ви потрябват. Това позволява по-естествен стил на кодиране и прави кода по-лесен за разбиране.
Може да определите няколко променливи във for оператор, но те трябва да бъдат от един и същ тип:
for(int i = 0, j = 1;
i < 10 && j != 11;
i++, j++)
/* тяло на for цикъла */;
Дефиницията на int във for цикъла е за i и j. Възможността да се дефинират променливи в управляващ израз е ограничена до for цикъла. Този подход не може да се използва с никакъв друг управляващ оператор.
По-рано в тази глава казах, че операторът запетая (не разделител, който се използва за разделяне на аргументите в списъка им)има само едно приложение в Java: в управляващия израз на for цикъл. И в инициализационния, и в стъпковия израз може да имате няколко оператора, разделени със запетая и те ще бъдат изчислявани последователно. Предишното парче код използва тази възможност. Ето друг пример:
//: c03:CommaOperator.java
public class CommaOperator {
public static void main(String[] args) {
for(int i = 1, j = i + 10; i < 5;
i++, j = i * 2) {
System.out.println("i= " + i + " j= " + j);
}
}
} ///:~
Ето какво се извежда:
i= 1 j= 11
i= 2 j= 4
i= 3 j= 6
i= 4 j= 8
Може да се види, че и в инициализационната и в стъпковата част операторите се изпълняват последователно. Също инициализационната част може да има всякакъв брой декларации от един тип.
break и continue
Вътре в тялото на всеки итерационен блок може да се управлява изпълнението чрез break и continue. break спира цикъла без да изпълнява останалите оператори в него. continue спира изпълнението на текущата итерация и отива в началото за започване на нова итерация.
Тази програма показва пример на break и continue вътре във for и while цикли:
//: c03:BreakAndContinue.java
// Demonstrates break and continue keywords
public class BreakAndContinue {
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
if(i == 74) break; // Out of for loop
if(i % 9 != 0) continue; // Next iteration
System.out.println(i);
}
int i = 0;
// An "infinite loop":
while(true) {
i++;
int j = i * 27;
if(j == 1269) break; // Out of loop
if(i % 10 != 0) continue; // Top of loop
System.out.println(i);
}
}
} ///:~
Във for стойността на i никога не става 100 поради това че break прекъсва цикъла когато i е 74. Нормално break се използва по този начин когато не се знае кога ще се случи условието за спиране на цикъла. continue операторът предава управлението в началото на цикъла за започване на нова итерация (инкрементирайки по този начин i) винаги щом i не се дели точно на 9. Когато се дели, извежда се променливата.
Втората част показва “безкраен цикъл” който на теория продължава вечно. Вътре в цикъла обаче има break оператор който ще прекъсне цикъла. В добавка ще видите, че continue отива обратно в началото без да изпълни останалото. (Това може би става когато i се дели на 9.) Изходът е:
0
9
18
27
36
45
54
63
72
10
20
30
40
Стойността 0 се извежда защото 0 % 9 дава 0.
Втората форма на безкраен цикъл е for(;;). Компилаторът третира и while(true) и for(;;) по еднакъв начин така че кой ще използвате е въпрос на програмистки вкус.
Безславното “goto”
Ключовата дума goto съществува в програмните езици от самото начало. Разбира се goto произхожда от управлението на хода на програмата в асемблерния език: “if условие A, then скочи тук, иначе скочи там.” Ако четете асемблерският код генериран от практически всеки компилатор ще видите че управлението на програмата съдържа много скокове (преходи -б.пр.). Обаче goto преходите са на ниво сорс и това е което им развали репутацията. Ако програмата винаги ще скача от една точка в друга, няма ли начин така да се организира програмата, че да не скача? goto изпадна в истинка немилост след известната публикация “Goto considered harmful” на Edsger Dijkstra и оттогава премахването на goto е популярен спорт.
Както е типично в подобни ситуации, средата е най-доброто нещо. Проблемът не е използването на goto ами прекаленото използване на goto и в редки ситуации goto е най-добрият начин за структуриране на програмата.
Въпреки че goto е запазена дума в Java тя не се използва в езика; Java няма goto. Има обаче нещо което изглежда малко като преход свързано с ключовите думи break и continue. Това не е скок а начин за излизане от цикъла. Причината често да се споменава в дискусиите за goto е че използва същия механизъм: етикет.
Етикетът е идентификатор следван от двоеточие както тук:
label1:
Единственото място където етикетът е полезен в Java е точно преди итерационен оператор. Именно точно преди – нищо хубаво не донася поставянето на друг оператор между етикета и цикъла. И единстветата примина да се слага етикет е ако смятате да слагате друга итерация или условен преход в първата. break и continue нормално ще прекъснат само текущия цикъл, но когато са използвани с етикет те ще прекъснат всичко до етикета:
label1:
outer-iteration {
inner-iteration {
//…
break; // 1
//…
continue; // 2
//…
continue label1; // 3
//…
break label1; // 4
}
}
В случай 1 break-ът прекъсва вътрешната итерация и отивате във външната. В случай 2 continue довежда обратно в началото на вътрешната итерация. Но в случай 3 continue label1 прекъсва вътрешната и външната итерация, винаги до label1. След това фактически итерациите продължават, но започвайки от външната итерация. В случай 4 break label1 също всичко до label1, но не започва пак итерация. Фактически излиза и от двете итерации.
Ето пример за for цикли:
//: c03:LabeledFor.java
// Java’s "labeled for loop"
public class LabeledFor {
public static void main(String[] args) {
int i = 0;
outer: // Can't have statements here
for(; true ;) { // infinite loop
inner: // Can't have statements here
for(; i < 10; i++) {
prt("i = " + i);
if(i == 2) {
prt("continue");
continue;
}
if(i == 3) {
prt("break");
i++; // Otherwise i never
// gets incremented.
break;
}
if(i == 7) {
prt("continue outer");
i++; // Otherwise i never
// gets incremented.
continue outer;
}
if(i == 8) {
prt("break outer");
break outer;
}
for(int k = 0; k < 5; k++) {
if(k == 3) {
prt("continue inner");
continue inner;
}
}
}
}
// Can't break or continue
// to labels here
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
Използва се prt( ) който беше определен в други примери.
Забележете че break извежда от for цикъла и няма инкрементиращ израз до края на for цикъла. Понеже break пропуска инкрементиращия израз инкрементирането се изпълнява директно за i == 3. Операторът continue outer в случая на I == 7 също скача в началото на цикъла и пропуска инкрементирането, така че тук отново се инкрементира директно.
Ето изхода:
i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer
Ако липсваше break outer операторът нямаше да има начин да се излезе от външния цикъл извътре на вътрешния, понеже break (без етикет - б.пр.) може само да прекъсне най-вътрешния цикъл. (Същото е вярно за continue.)
Разбира се, в случаите когато прекъсването на цикъла ще завърши и работата на метода може да се използва просто return.
Ето демонстрация на break и continue с етикети в while цикли:
//: c03:LabeledWhile.java
// Java's "labeled while" loop
public class LabeledWhile {
public static void main(String[] args) {
int i = 0;
outer:
while(true) {
prt("Outer while loop");
while(true) {
i++;
prt("i = " + i);
if(i == 1) {
prt("continue");
continue;
}
if(i == 3) {
prt("continue outer");
continue outer;
}
if(i == 5) {
prt("break");
break;
}
if(i == 7) {
prt("break outer");
break outer;
}
}
}
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
Същите правила се прилагат за while:
-
Без етикет continue скача в началото на най-вътрешния цикъл и продължава.
-
С етикет continue скача на етикета и започва цикъла след този етикет.
-
break “скача на дъното” на цикъл.
-
С етикет break скача на дъното на цикъла с етикета.
Изходът на този метод прави нещата по-ясни:
Outer while loop
i = 1
continue
i = 2
i = 3
continue outer
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer
Важно е да се запомни че единствената причина да се използват етикети в Java е когато имате вместени един в друг цикли и искате да break или continue през повече от едно ниво на вместване.
В “goto considered harmful” на Dijkstra той протестира фактически срещу етикетите, не срещу goto. Той забелязва, че броят на грешките изглежда расте с броя на етикетите в програмата. Етикетите и goto правят програмата мъчна за статичен анализ, понеже се въвеждат цикли в изпълнението. Забележете че в Java етикетите не страдат от този проблем, тъй като са ограничени и не могат да предават управлението по ad hoc маниер. Интересно е да се отбележи също, че това е случай в който една черта на езика е направена по-полезна с намаляване на мощта º.
switch
switch понякога се определя като оператор за избор. switch операторът избира измежду различни парчета кон на основата на цял израз. Формата му е:
switch(integral-selector) {
case integral-value1 : statement; break;
case integral-value2 : statement; break;
case integral-value3 : statement; break;
case integral-value4 : statement; break;
case integral-value5 : statement; break;
// …
default: statement;
}
Integral-selector е израз, който дава цяла стойност. switch сравнява резултата от integral-selector с всяка от integral-value. Ако намери съвпадение съответният statement (прост или съставен) се изпълнява. Ако не се намери съвпадение default statement се изпълнява.
Ще забележите че всеки case свършва с break, което предизвиква скок след края на тялата на switch. Това е конвенционалния метод за конструиране на switch оператора, но break е незадължителен. Ако е изпуснат се изпълнява кода на следващия по ред break. Въпреки че обикновено не се предпочита този род поведение, той може да бъде много полезен за напредналия програмист. Забележете че последния оператор, default, няма break понеже идпълнението продължава точно там, където би го продължил и break. Не бихте сложили break след default оператор ако считате стила за важно нещо.
Операторът switch е добър начин да се направи многопътна селекция (т.е избиране измежду много възможни пътища на изпълнение), но изисква селектор който дава цяла стойност като int или char. Ако искате, например, да използвате низ или плаваща запетая, това няма да работи със switch оператора. За нецели стойности трябва да използвате серия if оператори.
Ето пример в който се създават букви и се определя дали са гласни или съгласни:
//: c03:VowelsAndConsonants.java
// Demonstrates the switch statement
public class VowelsAndConsonants {
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
char c = (char)(Math.random() * 26 + 'a');
System.out.print(c + ": ");
switch(c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System.out.println("vowel");
break;
case 'y':
case 'w':
System.out.println(
"Sometimes a vowel");
break;
default:
System.out.println("consonant");
}
}
}
} ///:~
Тъй като Math.random( ) генерира числа между 0 и 1 необходимо е само да се умножи по горната граница на интервала числа, който искате да получите (26 за буквите на английската азбука) и да се добави отместване за да се получи долната граница.
Въпреки че изглежда че превключването става по знаци, switch операторът фактически използва цялата стоност на знака. Знаците с една кавичка в case операторите също дават цяла стойност която се използва за сравнение.
Забележете как caseтата могат да бъдат “стекирани” едно върху друго за да се получи избор на една част от кода за няколко стойности. Ще знаете също че е важно да се сложи break оператор във всяко отделно сравнение, иначе управлението ще продължи надолу с кода на следващото case.
Детайли на изчислението
Операторът:
char c = (char)(Math.random() * 26 + 'a');
заслужава поглед по-отблизо. Math.random( ) дава double, така че стойността 26 се превръща в double за да се изпълни умножението, което също дава double. Това значи че ‘a’ трябва да бъде превърнато в double за да се изпълни събирането. double резултатът се превръща обратно в char с кастинг.
Първо, какво прави касингът към char? Тоест, ако имате стойност 29.7 и го превръщате в char, 30 или 29 е стойността? Отговорът може да бъде видян в този пример:
//: c03:CastingNumbers.java
// What happens when you cast a float or double
// to an integral value?
public class CastingNumbers {
public static void main(String[] args) {
double
above = 0.7,
below = 0.4;
System.out.println("above: " + above);
System.out.println("below: " + below);
System.out.println(
"(int)above: " + (int)above);
System.out.println(
"(int)below: " + (int)below);
System.out.println(
"(char)('a' + above): " +
(char)('a' + above));
System.out.println(
"(char)('a' + below): " +
(char)('a' + below));
}
} ///:~
Изходът е:
above: 0.7
below: 0.4
(int)above: 0
(int)below: 0
(char)('a' + above): a
(char)('a' + below): a
Така че отговорът е: кастингът от float или double към цели стойности винаги реже.
Следващият въпрос е за Math.random( ). Дава ли той стойност от нула до единица включително ‘1’? На математически език (0,1), или [0,1], или (0,1] или [0,1)? (Квадратната скоба значи “включва” докато кръглата значи “не включва.”) Отново тестова програма дава отговора:
//: c03:RandomBounds.java
// Does Math.random() produce 0.0 and 1.0?
public class RandomBounds {
static void usage() {
System.err.println("Usage: \n\t" +
"RandomBounds lower\n\t" +
"RandomBounds upper");
System.exit(1);
}
public static void main(String[] args) {
if(args.length != 1) usage();
if(args[0].equals("lower")) {
while(Math.random() != 0.0)
; // Keep trying
System.out.println("Produced 0.0!");
}
else if(args[0].equals("upper")) {
while(Math.random() != 1.0)
; // Keep trying
System.out.println("Produced 1.0!");
}
else
usage();
}
} ///:~
За да стартирате програмата пишете:
java RandomBounds lower
или
java RandomBounds upper
В двата случая трябва да прекъснете програмата ръчно, така че ще изглежда че Math.random( ) никога не дава 0.0 и 1.0. Експериментът тук обаче може да заблуждава. Ако считаме че има 2128 различни мантиси с плаваща запетая между 0 и 1, вероятността да се уцели точно може да е такава, че съответното време да е по-дълго от времето на живот на всеки компютър, а и на експериментатора. Излиза че 0.0 се включва във възможния изход на Math.random( ). Или на математически език [0,1).
Сподели с приятели: |