Смекни!
smekni.com

Общие представления о языке Java 5 (стр. 32 из 68)

Такого рода действия очень характерны при написании программ с использованием циклов, и особенно важны при большом количестве шагов: следует выносить из циклов все операции, которые могут быть проделаны однократно вне цикла. Но при усовершенствованиях часто теряется прозрачность алгоритмов. Поэтому полезно сначала написать реализацию алгоритма “один к одному” по формулам, без всяких усовершенствований, и убедиться при не очень большом числе шагов, что всё работает правильно. А уже затем можно вносить исправления, повышающие скорость работы программы в наиболее критических местах. Не следует сразу пытаться написать программу, которая максимально эффективна по всем параметрам. Это обычно приводит к гораздо более длительному процессу поиска неочевидных ошибок в такой программе.

Замечание: В Java отсутствует специальная форма оператора for для перебора в цикле элементов массивов и коллекций (или, что то же, наборов). Тем не менее оператор for позволяет последовательно обработать все элементы массива или набора. Пример поочерёдного вывода диалогов со значениями свойств компонентов, являющихся элементами массива компонентов главной формы приложения:

java.util.List components= java.util.Arrays.asList(this.getComponents());

for (Iterator iter = components.iterator();iter.hasNext();) {

Object elem = (Object) iter.next();

javax.swing.JOptionPane.showMessageDialog(null,"Компонент: "+

elem.toString());

}

Оператор цикла while – цикл с предусловием

while(условие)

оператор;

Пока условие сохраняет значение true — в цикле выполняется оператор, иначе — действие цикла прекращается. Если условие с самого начала false, цикл сразу прекращается, и тело цикла не выполнится ни разу.

Цикл while обычно применяют вместо цикла for в том случае, если условия продолжения достаточно сложные. В отличие от цикла for в этом случае нет формально заданного счётчика цикла, и не производится его автоматического изменения. За это отвечает программист. Хотя вполне возможно использование как цикла for вместо while , так и наоборот. Многие программисты предпочитают пользоваться только циклом for как наиболее универсальным.

Пример:

i=1;

x=0;

while(i<=n){

x+=i;//эквивалентно x=x+i;

i*=2;//эквивалентно i=2*i;

};

В операторе while очень часто совершают ошибки, приводящие к неустойчивости алгоритмов из-за сравнения чисел с плавающей точкой на неравенство. Как мы знаем, сравнивать их на равенство в подавляющем большинстве случаев некорректно из-за ошибок представления таких чисел в компьютере. Но большинство программистов почему-то считает, что при сравнении на неравенство проблем не возникает, хотя это не так. Например, если организовать с помощью оператора while цикл с вещественным счётчиком, аналогичный разобранному в разделе, посвящённому циклу for. Пример типичной ошибки в организации такого цикла приведён ниже:

double a=…;

double b=…;

double dx=…;

double x=a;

while(x<=b){

x=x+dx;

};

Как мы уже знаем, данный цикл будет обладать неустойчивостью в случае, когда на интервале от a до b укладывается целое число шагов. Например, при a=0, b=10, dx=0.1 тело цикла будет выполняться при x=0, x=0.1, …, x=9.9. А вот при x=10 тело цикла может либо выполниться, либо не выполниться – как повезёт! Причина связана с конечной точностью выполнения операций с числами в формате с плавающей точкой. Величина шага dx в двоичном представлении чуть-чуть отличается от значения 0.1, и при каждом цикле систематическая погрешность в значении x накапливается. Поэтому точное значение x=10 достигнуто не будет, величина x будет либо чуть-чуть меньше, либо чуть-чуть больше. В первом случае тело цикла выполнится, во втором – нет. То есть пройдёт либо 100, либо 101 итерация (число выполнений тела цикла).

Оператор цикла do...while – цикл с постусловием

do

оператор;

while(условие);

Если условие принимает значение false, цикл прекращается. Тело цикла выполняется до проверки условия, поэтому оно всегда выполнится хотя бы один раз.

Пример:

int i=0;

double x=1;

do{

i++; // i=i+1;

x*=i; // x=x*i;

}

while(i<n);

Если с помощью оператора do…while организуется цикл с вещественным счётчиком или другой проверкой на равенство или неравенство чисел типа float или double, у него возникают точно такие же проблемы, как описанные для циклов for и while.

При необходимости организовать бесконечный цикл (с выходом изнутри тела цикла с помощью оператора прерывания) часто используют следующий вариант:

do{

}

while(false);

Операторы прерывания continue, break, return, System.exit

Довольно часто требуется при выполнении какого-либо условия прервать цикл или подпрограмму и перейти к выполнению другого алгоритма или очередной итерации цикла. При неструктурном программировании для этих целей служил оператор goto. В Java имеются более гибкие и структурные средства для решения этих проблем - операторы continue, break, return, System.exit:

continue; – прерывание выполнения тела цикла и переход к следующей итерации (проверке условия) текущего цикла;

continue имя метки; – прерывание выполнения тела цикла и переход к следующей итерации (проверке условия) цикла, помеченного меткой (label);

break; – выход из текущего цикла;

break имя метки; – выход из цикла, помеченного меткой;

return; – выход из текущей подпрограммы (в том числе из тела цикла) без возврата значения;

return значение; – выход из текущей подпрограммы (в том числе из тела цикла) с возвратом значения;

System.exit(n) –выход из приложения с кодом завершения n. Целое число n произвольно задаётся программистом. Если n=0, выход считается нормальным, в других случаях - аварийным. Приложение перед завершением сообщает число n операционной системе для того, чтобы программист мог установить, по какой причине произошёл аварийный выход.

Операторы continue и break используются в двух вариантах – без меток для выхода из текущего (самого внутреннего по вложенности) цикла, и с меткой - для выхода из помеченного ей цикла. Меткой является идентификатор, после которого стоит двоеточие. Метку можно ставить непосредственно перед ключевым словом, начинающим задание цикла (for, while, do).

Пример использования continue без метки:

for(int i=1;i<=10;i++){

if(i==(i/2)*2){

continue;

};

System.out.println("i="+i);

};

В данном цикле не будут печататься все значения i, для которых i==(i/2)*2. То есть выводиться в окно консоли будут только нечётные значения i.

Ещё один пример использования continue без метки:

for(int i=1;i<=20;i++){

for(int j=1;j<=20;j++){

if(i*j==(i*j/2)*2){

continue;

};

System.out.println("i="+i+" j="+j+ " 1.0/(i*j-20)="+ (1.0/(i*j-20)) );

};

};

В этом случае будут выводиться значения i, j и 1.0/(i*j-20) для всех нечётных i и j от 1 до 19 . То есть будут пропущены значения для всех чётных i и j:

i=1 j=1 1.0/(i*j-20)=-0.05263157894736842

i=1 j=3 1.0/(i*j-20)=-0.058823529411764705

i=1 j=5 1.0/(i*j-20)=-0.06666666666666667

i=1 j=7 1.0/(i*j-20)=-0.07692307692307693

i=1 j=9 1.0/(i*j-20)=-0.09090909090909091

i=1 j=11 1.0/(i*j-20)=-0.1111111111111111

i=1 j=13 1.0/(i*j-20)=-0.14285714285714285

i=1 j=15 1.0/(i*j-20)=-0.2

i=1 j=17 1.0/(i*j-20)=-0.3333333333333333

i=1 j=19 1.0/(i*j-20)=-1.0

i=3 j=1 1.0/(i*j-20)=-0.058823529411764705

i=3 j=3 1.0/(i*j-20)=-0.09090909090909091

i=3 j=5 1.0/(i*j-20)=-0.2

i=3 j=7 1.0/(i*j-20)=1.0

...

i=19 j=9 1.0/(i*j-20)=0.006622516556291391

i=19 j=11 1.0/(i*j-20)=0.005291005291005291

i=19 j=13 1.0/(i*j-20)=0.004405286343612335

i=19 j=15 1.0/(i*j-20)=0.0037735849056603774

i=19 j=17 1.0/(i*j-20)=0.0033003300330033004

i=19 j=19 1.0/(i*j-20)=0.002932551319648094

Пример использования continue с меткой:

label_for1:

for(int i=1;i<=20;i++){

for(int j=1;j<=20;j++){

if(i*j==(i*j/2)*2){

continue label_for1;

};

System.out.println("i="+i+" j="+j+ " 1.0/(i*j-20)="+ (1.0/(i*j-20)) );

};

};

В отличие от предыдущего случая, после каждого достижения равенства i*j==(i*j/2)*2 будет производиться выход из внутреннего цикла (по j), и все последующие j для таких значений i будут пропущены. Поэтому будут выведены только значения

i=1 j=1 1.0/(i*j-20)=-0.05263157894736842

i=3 j=1 1.0/(i*j-20)=-0.058823529411764705

i=5 j=1 1.0/(i*j-20)=-0.06666666666666667

i=7 j=1 1.0/(i*j-20)=-0.07692307692307693

i=9 j=1 1.0/(i*j-20)=-0.09090909090909091

i=11 j=1 1.0/(i*j-20)=-0.1111111111111111

i=13 j=1 1.0/(i*j-20)=-0.14285714285714285

i=15 j=1 1.0/(i*j-20)=-0.2

i=17 j=1 1.0/(i*j-20)=-0.3333333333333333

i=19 j=1 1.0/(i*j-20)=-1.0

Пример использования break без метки:

for(int i=1;i<=10;i++){

if( i+6== i*i ){

break;

};

System.out.println("i="+i);

};

Данный цикл остановится при выполнении условия i+6== i*i . То есть вывод в окно консоли будет только для значений i, равных 1 и 2.

Ещё один пример использования break без метки:

for(int i=1;i<=20;i++){

for(int j=1;j<=20;j++){

if(i*j==(i*j/2)*2){

break;

};

System.out.println("i="+i+" j="+j+ " 1.0/(i*j-20)="+ (1.0/(i*j-20)) );

};

};

В этом случае будут выводиться все значения i и j до тех пор, пока не найдётся пара i и j, для которых i*j==(i*j/2)*2. После чего внутренний цикл прекращается – значения i, j и 1.0/(i*j-20) для данного и последующих значений j при соответствующем i не будут выводиться в окно консоли. Но внешний цикл (по i) будет продолжаться, и вывод продолжится для новых i и j. Результат будет таким же, как для continue с меткой для внешнего цикла.

Пример использования break с меткой:

label_for1:

for(int i=1;i<=20;i++){

for(int j=1;j<=20;j++){

if(i*j==(i*j/2)*2){

break label_for1;

};

System.out.println("i="+i+" j="+j+ " 1.0/(i*j-20)="+ (1.0/(i*j-20)) );

};

};

В этом случае также будут выводиться все значения i и j до тех пор, пока не найдётся пара i и j, для которых i*j==(i*j/2)*2. После чего прекращается внешний цикл, а значит – и внутренний тоже. Так что вывод в окно консоли прекратится. Поэтому вывод будет только для i=1, j=1.