Книга е още в много ранна фаза на написване


Управление на изпълнението



страница23/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   19   20   21   22   23   24   25   26   ...   73

Управление на изпълнението


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:

  1. Без етикет continue скача в началото на най-вътрешния цикъл и продължава.

  2. С етикет continue скача на етикета и започва цикъла след този етикет.

  3. break “скача на дъното” на цикъл.

  4. С етикет 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).




Сподели с приятели:
1   ...   19   20   21   22   23   24   25   26   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница