Оператор цикла for
for(блок инициализации; условие выполнения тела цикла;
блок изменения счётчиков)
оператор;
В блоке инициализации через запятую перечисляются операторы задания локальных переменных, область существования которых ограничивается оператором for. Также могут быть присвоены значения переменным, заданным вне цикла. Но инициализация может происходить только для переменных одного типа.
В блоке условия продолжения цикла проверяется выполнение условия, и если оно выполняется, идёт выполнение тела цикла, в качестве которого выступает оператор. Если же не выполняется – цикл прекращается, и идёт переход к оператору программы, следующему за оператором for.
После каждого выполнения тела цикла (очередного шага цикла) выполняются операторы блока изменения счётчиков. Они должны разделяться запятыми.
Пример:
for(int i=1,j=5; i+j<100; i++,j=i+2*j){
...
};
Каждый из блоков оператора for является необязательным, но при этом разделительные “;” требуется писать.
Наиболее употребительное использование оператора for – для перебора значений некоторой переменной, увеличивающихся или уменьшающихся на 1, и выполнения последовательности операторов, использующих эти значения. Переменная называется счетчиком цикла, а последовательности операторов – телом цикла.
Пример1: вычисление суммы последовательно идущих чисел.
Напишем цикл, в котором производится суммирование всех чисел от 1 до 100. Результат будем хранить в переменной result.
int result=0;
for(int i=1; i<=100; i++){
result=result+i;
};
Цикл (повторное выполнение одних и тех же действий) выполняется следующим образом:
До начала цикла создаётся переменная result, в которой мы будем хранить результат. Одновременно выполняется инициализация – присваивается начальное значение 0.
Начинается цикл. Сначала выполняется блок инициализации - счётчику цикла i присваивается значение 1. Блок инициализации выполняется только один раз в самом начале цикла.
Начинается первый шаг цикла. Проверяется условие выполнения цикла. Значение i сравнивается со 100.
Поскольку сравнение 1<=100 возвращает true, выполняется тело цикла. В переменной result хранится 0, а значение i равно 1, поэтому присваивание result=result+i эквивалентно result=1. Таким образом, после первого шага цикла в переменной result будет храниться значение 1.
После выполнения тела цикла выполняется секция изменения счётчика цикла, то есть оператор i++, увеличивающий i на 1. Значение i становится равным 2.
Начинается второй шаг цикла. Проверяется условие выполнения тела цикла. Поскольку сравнение 2<=100 возвращает true, идёт очередное выполнение тела цикла, а затем – увеличение счётчика цикла.
Шаги цикла продолжаются до тех пор, пока счётчик цикла не станет равным 101. В этом случае условие выполнения тела цикла 101<=100 возвращает false, и происходит выход из цикла. Последнее присваивание result=result+i, проведённое в цикле, это result=result+100.
Если бы нам надо было просуммировать числа от 55 до 1234, в блоке инициализации i надо присвоить 55, а в условии проверки поставить 1234 вместо 100.
Пример 2: вычисление факториала.
double x=1;
for(i=1;i<=n;i++){
x=x*i;
};
Заметим, что в приведённых примерах можно сделать некоторые усовершенствования -операторы присваивания записать следующим образом:
result+=i; вместо result=result+i;
для первого примера и
x*=i; вместо x=x*i;
для второго. На начальных стадиях обучения так лучше не делать, поскольку текст программы должен быть понятен программисту – все алгоритмы должны быть “прозрачны” для понимания.
Наиболее распространённая ошибка при работе с циклами, в том числе – с циклом for - использование вещественного счётчика цикла. Разберём эту ошибку на примере.
Пример 3. Вычисление площади под кривой.
Пусть надо вычислить площадь S под кривой, задаваемой функцией f(x), на промежутке от a до b (провести интегрирование). Разобьём промежуток на n интервалов, при этом длина каждого интервала будет h=(b-a)/n. Мы предполагаем, что величины a,b,n и функция f(x) заданы. Площадь под кривой на интервале с номером j будем считать равной значению функции на левом конце интервала. Такой метод численного нахождения интеграла называется методом левых прямоугольников.
На первый взгляд можно записать алгоритм вычисления этой площади в следующем виде:
double S=0;
double h=(b-a)/n;
for(double x=a;x<b;x=x+h){
S=S+f(x)*h;
};
И действительно, ИНОГДА такой алгоритм правильно работает. Но изменение числа интервалов n или границ a или b может привести к тому, что будет учтён лишний интервал, находящийся справа от точки b. Это связано с тем, что в циклах с вещественным счётчиком ВСЕГДА проявляется нестабильность, если последняя точка попадает на границу интервала. Что будет происходить на последних шагах нашего цикла?
На предпоследнем шаге мы попадаем в точку x=b-h, при этом условие x<b всегда выполняется, и никаких проблем не возникает. Следующей точкой должна быть x=b. Проверяется условие x<b, и в идеальном случае должен происходить выход из цикла, поскольку x==b, и условие не должно выполняться. Но ведь все операции в компьютере для чисел в формате с плавающей точкой проводятся с конечной точностью. Поэтому практически всегда значение x для этого шага будет либо чуть меньше b, либо чуть больше. Отличие будет в последних битах мантиссы, но этого окажется достаточно для того, чтобы сравнение x<b иногда давало true. Хотя для заданных a, b и n результат будет прекрасным образом воспроизводиться, в том числе – на других компьютерах. Более того, при увеличении числа разбиений n погрешность вычисления площади даже для “неправильного” варианта будет убывать, хотя и гораздо медленнее, чем для “правильного”. Это один из самых неприятных типов ошибок, когда алгоритм вроде бы работает правильно, но для получения нужной точности требуется гораздо больше времени или ресурсов.
Рассмотрим теперь правильную реализацию алгоритма. Для этого будем использовать ЦЕЛОЧИСЛЕННЫЙ счётчик цикла. Нам потребуется чуть больше рассуждений, и алгоритм окажется немного менее прозрачным, но зато гарантируется его устойчивость. Значение функции в начале интервала с номером j будет равна f(a+j*h). Первый интервал будет иметь номер 0, второй – номер 1, и так далее. Последний интервал будет иметь номер n-1.
Правильно работающий алгоритм может выглядеть так:
double S=0;
double h=(b-a)/n;
for(int j=0;j<=n-1;j++){
S=S+f(a+j*h)*h;
};
Проверить неустойчивость первого алгоритма и устойчивость второго можно на примере f(x)=x2, a=0, b=1.
-
n
S для первого алгоритма
S для второго алгоритма
8
0.2734375
0.2734375
9
0.2798353909465021
0.279835390946502
10
0.3849999999999999
0.2850000000000001
11
0.28925619834710753
0.28925619834710750
12
0.29282407407407407
0.292824074074074
13
0.3727810650887573
0.2958579881656805
…
…
…
100
0.32835000000000036
0.32835000000000014
101
0.32839917655131895
0.3283991765513185
102
0.33825131359733385
0.3284473920287069
103
0.3382034121971908
0.3284946743331133
…
…
…
1000
0.33283350000000095
0.33283350000000034
1001
0.3338330001666631
0.33283399916766554
1002
0.3328344973393309
0.33283449733932
В таблице жирным выделены первые значащие цифры неправильных значений, получающихся в результате неустойчивости алгоритма при n=10, n=13, n=102, n=103, n=1001. При отсутствии неустойчивости оба алгоритма при всех n должны были бы давать одинаковые результаты (с точностью до нескольких младших бит мантиссы). Очень характерной особенностью такого рода неустойчивостей является скачкообразное изменение результата при плавном изменении какого-либо параметра. В приведённой выше таблице меняется число n, но такая же ситуация будет наблюдаться и при плавном изменении чисел a или b. Например, при n=10 и a=0 получим следующие результаты в случае очень малых изменений b:
-
b
S для первого алгоритма
S для второго алгоритма
1.00000
0.3849999999999999
0.2850000000000001
1.00001
0.28500855008550036
0.28500855008550036
1.00002
0.2850171003420023
0.2850171003420022
1.00003
0.38503465103951023
0.28502565076950764
1.00004
0.2850342013680182
0.2850342013680184
1.00005
0.2850427521375357
0.2850427521375357
Вещественный счётчик цикла не всегда приводит к проблемам. Рассмотрим вариант численного нахождения интеграла методом средних прямоугольников. Для этого площадь под кривой на интервале с номером j будем считать равной значению функции в середине интервала. Алгоритм идентичен описанному выше для метода левых прямоугольников, за исключением выбора в качестве начальной точки a+h/2 вместо a.
double S=0;
h=(b-a)/n;
for(double x=a+h/2;x<b;x=x+h){
S=S+f(x)*h;
};
Для данного алгоритма проблем не возникает благодаря тому, что все точки x, в которых производится сравнение x<b, отстоят достаточно далеко от значения b – по крайней мере на h/2. Заметим, что метод средних прямоугольников гораздо точнее метода левых прямоугольников, и в реальных вычислениях лучше использовать либо его, либо метод Симпсона, который несколько сложнее, но обычно ещё более точен.
Отметим ещё один момент, важный для эффективной организации циклов. Предложенные выше реализации циклов не самые эффективные по скорости, поскольку в них много раз повторяется достаточно медленная операция – “вещественное” умножение (умножение в формате с плавающей точкой). Лучше написать алгоритм метода средних прямоугольников так:
double S=0;
double h=(b-a)/n;
for(double x=a+h/2;x<b;x=x+h){
S=S+f(x);
};
S=S*h;
При этом суммируются значения f(x), а не f(x)*h, а умножение делается только один раз – уже после выхода из цикла для всей получившейся суммы. Если время выполнения “вещественного”умножения tумнож, то экономия времени работы программы по сравнению с первоначальным вариантом будет (n-1)* tумнож. Для больших значений n экономия времени может быть существенной даже для мощных компьютеров.
Такого рода действия очень характерны при написании программ с использованием циклов, и особенно важны при большом количестве шагов: следует выносить из циклов все операции, которые могут быть проделаны однократно вне цикла. Но при усовершенствованиях часто теряется прозрачность алгоритмов. Поэтому полезно сначала написать реализацию алгоритма “один к одному” по формулам, без всяких усовершенствований, и убедиться при не очень большом числе шагов, что всё работает правильно. А уже затем можно вносить исправления, повышающие скорость работы программы в наиболее критических местах. Не следует сразу пытаться написать программу, которая максимально эффективна по всем параметрам. Это обычно приводит к гораздо более длительному процессу поиска неочевидных ошибок в такой программе.
Замечание: В 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());
}
- Содержание
- Глава 1. Общие представления о языке Java 6
- Глава 2. Объектно-ориентированное проектирование и платформа NetBeans 26
- Глава 3. Примитивные типы данных и операторы для работы с ними 78
- Глава 4. Работа с числами в языке Java 95
- Глава 5. Управляющие конструкции 112
- Глава 6. Начальные сведения об объектном программировании 128
- Глава 7. Важнейшие объектные типы 175
- Введение
- Глава 1. Общие представления о языке Java
- 1.1. Java и другие языки программирования. Системное и прикладное программирование
- 1.2. Виртуальная Java-машина, байт-код, jit-компиляция. Категории программ, написанных на языке Java
- 1.3.Алфавит языка Java. Десятичные и шестнадцатеричные цифры и целые числа. Зарезервированные слова Алфавит языка Java
- Десятичные и шестнадцатеричные цифры и целые числа
- Зарезервированные слова языка Java
- 1.4. Управляющие последовательности. Символы Unicode. Специальные символы Управляющие последовательности
- Простые специальные символы
- Составные специальные символы
- 1.5.Идентификаторы. Переменные и типы. Примитивные и ссылочные типы
- Краткие итоги по главе 1
- Задания
- Глава 2. Объектно-ориентированное проектирование и платформа NetBeans
- 2.1.Процедурное и объектно-ориентированное программирование. Инкапсуляция
- 2.2. Работа со ссылочными переменными. Сборка мусора
- 2.3. Проекты NetBeans. Пакеты. Уровни видимости классов. Импорт классов
- 2.4. Базовые пакеты и классы Java
- 2.5. Создание в NetBeans простейшего приложения Java
- 2.6. Компиляция файлов проекта и запуск приложения
- 2.7. Структура проекта NetBeans
- 2.8. Создание в NetBeans приложения Java с графическим интерфейсом
- 2.9. Редактор экранных форм
- 2.10. Внешний вид приложения
- 2.11. Ведение проектов
- 2.11. Редактирование меню экранной формы
- 2.12. Создание нового класса
- 2.13. Документирование исходного кода в Java
- 2.14. Основные компоненты пакетов swing и awt
- 2.15. Технологии Java и .Net
- Краткие итоги по главе 2
- Задания
- Глава 3. Примитивные типы данных и операторы для работы с ними
- 3.1.Булевский (логический) тип
- 3.2.Целые типы, переменные, константы
- 3.3.Основные операторы для работы с целочисленными величинами
- 3.4.Вещественные типы и класс Math
- 3.5.Правила явного и автоматического преобразования типа при работе с числовыми величинами
- 3.6. Оболочечные классы. Упаковка (boxing) и распаковка (unboxing)
- 3.7.Приоритет операторов
- 3.8.Типы-перечисления (enum)
- Краткие итоги по главе 3
- Задания
- Глава 4. Работа с числами в языке Java
- 4.1 Двоичное представление целых чисел Позиционные и непозиционные системы счисления
- Двоичное представление положительных целых чисел
- Двоичное представление отрицательных целых чисел. Дополнительный код
- Проблемы целочисленной машинной арифметики
- Шестнадцатеричное представление целых чисел и перевод из одной системы счисления в другую
- 4.2. Побитовые маски и сдвиги
- 4.3. Двоичное представление вещественных чисел Двоичные дроби
- Мантисса и порядок числа
- Стандарт ieee 754 представления чисел в формате с плавающей точкой*
- Краткие итоги по главе 4
- Задания
- Глава 5. Управляющие конструкции Составной оператор
- Условный оператор if
- Оператор выбора switch
- Условное выражение …?... : …
- Оператор цикла for
- Оператор цикла while – цикл с предусловием
- Оператор цикла do...While – цикл с постусловием
- Операторы прерывания continue, break, return, System.Exit
- Краткие итоги по главе 5
- Задания
- Глава 6. Начальные сведения об объектном программировании
- Наследование и полиморфизм. Uml-диаграммы
- Функции. Модификаторы. Передача примитивных типов в функции
- Локальные и глобальные переменные. Модификаторы доступа и правила видимости. Ссылка this
- Передача ссылочных типов в функции. Проблема изменения ссылки внутри подпрограммы
- Наследование. Суперклассы и подклассы. Переопределение методов
- Наследование и правила видимости. Зарезервированное слово super
- Статическое и динамическое связывание методов. Полиморфизм
- Базовый класс Object
- Конструкторы. Зарезервированные слова super и this. Блоки инициализации
- Удаление неиспользуемых объектов и метод finalize. Проблема деструкторов для сложно устроенных объектов
- Перегрузка методов
- Правила совместимости ссылочных типов как основа использования полиморфного кода. Приведение и проверка типов
- Рефакторинг
- Reverse engineering – построение uml-диаграмм по разработанным классам
- Краткие итоги по главе 6
- Задания
- Глава 7. Важнейшие объектные типы Массивы
- Коллекции, списки, итераторы
- Работа со строками в Java. Строки как объекты. Классы String, StringBuffer и StringBuilder
- Работа с графикой
- Исключительные ситуации Обработка исключительных ситуаций
- Иерархия исключительных ситуаций
- Объявление типа исключительной ситуации и оператор throw
- Объявление метода, который может возбуждать исключительную ситуацию. Зарезервированное слово throws
- Работа с файлами и папками
- Краткие итоги по главе 7
- Задания
- Глава 8. Наследование: проблемы и альтернативы. Интерфейсы. Композиция Проблемы множественного наследования классов. Интерфейсы
- Отличия интерфейсов от классов. Проблемы наследования интерфейсов
- Пример на использование интерфейсов
- Композиция как альтернатива множественному наследованию
- Краткие итоги по главе 8
- Задания
- Глава 9. Дополнительные элементы объектного программирования на языке Java Потоки выполнения (threads) и синхронизация
- Преимущества и проблемы при работе с потоками выполнения
- Синхронизация по ресурсам и событиям
- Класс Thread и интерфейс Runnable. Создание и запуск потока выполнения
- Поля и методы, заданные в классе Thread
- Подключение внешних библиотек dll.“Родные” (native) методы*
- Краткие итоги по главе 9
- Задания
- Глава 10. Введение в сетевое программирование Краткая справка по языку html
- Апплеты
- Сервлеты
- Технология jsp – Java Server Pages
- Краткие итоги по главе 10
- Задания
- Глава 11. Встроенные классы Виды встроенных классов
- Вложенные (nested) классы и интерфейсы
- Внутренние (inner) классы
- Локальные (local) классы
- Анонимные (anonimous) классы и обработчики событий
- Анонимные (anonimous) классы и слушатели событий (listeners)
- Краткие итоги по главе 11
- Задания
- Глава 12. Компонентное программирование Компонентная архитектура JavaBeans
- Мастер создания компонента в NetBeans
- Пример создания компонента в NetBeans – панель с заголовком
- Добавление в компонент новых свойств
- Добавление в компонент новых событий
- Краткие итоги по главе 12
- Задания
- Литература
- Дополнительная литература
- 276 Курс подготовлен при поддержке Sun Microsystems