3.2 Выполнение контроля
Java использует все конструкции управления, присутствующие в C, поэтому если вы ранее программировали на C или C++, большинство из этих конструкций должно быть вам знакомым. Большинство процедурных языков программирования предоставляют некоторые формы конструкций управления, которые обычно являются общими для всех языков. В Java ключевые слова включают if-else
, while
, do-while
, for
и выборочное выражение switch
. Однако Java не поддерживает очень опасное goto
(хотя это может быть временным решением для некоторых специфических проблем). Тем не менее можно выполнять переходы, аналогичные goto
, но они гораздо более ограничены.
A == B
. Оператор сравнения ==
проверяет, равно ли значение A значению B. Это выражение возвращает true
или false
. Все отношения операторов, представленные ранее в этой главе, могут использоваться для создания условных выражений. Обратите внимание, что Java не позволяет использовать число как булевое значение, даже если это допустимо в C и C++ (где любое ненулевое значение считается истиной, а ноль — ложью). Если вы хотите использовать некий небулевой тип в качестве условия — например, в if(a)
, то сначала его следует преобразовать в булево значение с помощью условного выражения, например, if(a != 0)
.## 3.2.2 if-else
Конструкция if-else
является одной из самых базовых форм управления потоком выполнения. Часть else
является необязательной, поэтому if
может использоваться в двух формах:
if (булевское выражение)
выражение
или
if (булевское выражение)
выражение
else
выражение
Условие должно давать булевый результат. "Выражение" либо представляет собой простое выражение, заканчивающееся точкой с запятой, либо составное выражение — группу простых выражений, заключённых в фигурные скобки. В любом месте этого руководства, где упоминается слово "выражение", возможно, имеют в виду как простое, так и составное выражение.
В качестве примера использования 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; // совпадение
return result;
}
Лучше всего организовать конструкции управления потоком выполнения с отступами, чтобы читатели могли легко определить начало и конец каждого блока.
return
Ключевое слово return
имеет две основные функции: указание значения, которое метод должен вернуть (если он не имеет void
типа возврата), и немедленное возвращение этого значения. На основе этого можно переопределить метод test()
, чтобы он использовал эти возможности:static int test2(int testval) {
if (testval > target)
return -1;
if (testval < target)
return +1;
return 0; // совпадение
}
```не стоит добавлять `else`, так как метод прекращает выполнение после встречи с `return`.
## 3.2.3 Итерация
`while`, `do-while` и `for` контролируют цикл, иногда их называют "операторами итерации". Инструкция будет повторяться до тех пор, пока булево выражение не вернет значение "ложь". Формат `while` цикла следующий:
while(булево выражение) инструкция
При первом входе в цикл вычисляется одно значение "булевого выражения". Для каждого последующего входа в цикл, перед каждым новым проходом, это выражение снова вычисляется.
Ниже приведен простой пример, который генерирует случайные числа до тех пор, пока они не удовлетворят определенному условию:
//: WhileTest.java // Демонстрация оператора while
public class WhileTest { public static void main(String[] args) { double r = 0; while(r < 0.99d) { r = Math.random(); System.out.println(r); } } } ///:~
Он использует статический метод `random()` из библиотеки `Math`. Этот метод генерирует число типа `double` между 0 и 1 (включая 0, но не включая 1). Условие цикла `while` означает: "повторять цикл до тех пор, пока число меньше 0.99". Из-за своей случайной природы каждый запуск программы будет давать список чисел различной длины.
## 3.2.4 `do-while`
Формат `do-while` следующий:
do
инструкция
while(булево выражение)
```Единственная разница между while
и `do-while` заключается в том, что `do-while` обязательно выполняется хотя бы один раз; то есть, инструкция всегда будет выполнена хотя бы один раз, даже если выражение сразу вернёт значение "ложь". В случае `while` цикла, если условие сразу вернёт значение "ложь", инструкция вообще не будет выполнена. На практике `while` используется чаще, чем `do-while`.## 3.2.5 `for`
Перед первой итерацией for
происходит инициализация. Затем производится проверка условия, а затем выполняется некоторый шаг ("шагание") во время каждой итерации. Формат for
цикла следующий:
for(начальное выражение; булево выражение; шаг)
инструкция
Любое начальное выражение, булево выражение или шаг могут быть пустыми. Перед каждой итерацией проверяется булево выражение. Если результат равен "ложь", выполнение продолжается со следующей строки после for
инструкции. После каждой итерации вычисляется шаг. Цикл for
обычно используется для выполнения задач «подсчета»:
//: ListCharacters.java
// Демонстрация цикла "for", выводящего все ASCII-символы.
public class ListCharacters {
public static void main(String[] args) {
for(char c = 0; c < 128; c++) {
if(c != 26) { // ANSI Очистка экрана
System.out.println(
"значение: " + (int)c +
" символ: " + c);
}
}
} ///:~
}
Обратите внимание, что переменная c
определяется в месте её использования — внутри контрольного выражения цикла for
, а не в начале блока кода, отмеченного начальной фигурной скобкой. Область видимости переменной c
ограничивается контрольным выражением цикла for
.В отличие от традиционных процедурных языков программирования, таких как C, где требуется объявление всех переменных в начале блока кода, Java и C++ позволяют объявлять переменные в любом месте блока кода, где они действительно необходимы. Это позволяет создавать более естественный стиль кодирования и улучшает его понимание.Можно объявить несколько переменных внутри выражения цикла for
, но они должны иметь одинаковый тип:
for(int i = 0, j = i + 10; i < 10 && j != 11; i++, j++) {
// тело цикла for
};
Здесь объявление int
одновременно определяет и i
, и j
. Возможность объявления переменных внутри контрольного выражения цикла for
доступна только для этого типа цикла. Для других условий или циклов такой подход недопустим.
Как было упомянуто в главе 1, запятая может использоваться как оператор, а не просто разделитель. В Java единственным местом, где используется запятая как оператор, является контрольное выражение цикла for
. В части инициализации и шага контрольного выражения можно использовать последовательность выражений, разделённых запятыми. Все эти выражения выполняются последовательно. Пример выше уже демонстрировал это, вот ещё один пример:
//: 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
Как видно, выражения в части инициализации и шага выполняются последовательно. Кроме того, хотя в части инициализации могут быть указаны любое количество объявлений, они должны иметь одинаковый тип.## 3.2.6 Прерывание и продолжениеВ теле любого циклического оператора можно использовать break
и continue
, чтобы контролировать ход выполнения цикла. Оператор break
используется для принудительного выхода из цикла, пропуская все последующие операторы в цикле. В то время как оператор continue
прекращает выполнение текущей итерации и переходит к следующей итерации.
Ниже представлен программный пример, демонстрирующий использование break
и continue
в циклах for
и while
:
//: BreakAndContinue.java
// Демонстрация ключевых слов break и continue
public class BreakAndContinue {
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
if(i == 74) break; // Выход из цикла for
if(i % 9 != 0) continue; // Начало новой итерации
System.out.println(i);
}
int i = 0;
// Бесконечный цикл:
while(true) {
i++;
int j = i * 27;
if(j == 1269) break; // Выход из цикла
if(i % 10 != 0) continue; // Начало новой итерации
System.out.println(i);
}
}
} ///:~
В этом цикле for
, значение переменной i
никогда не достигнет значения 100. Как только значение i
становится равным 74, оператор break
прерывает выполнение цикла. Обычно, оператор break
используется тогда, когда условие завершения цикла не известно заранее. Если значение i
не делится нацело на 9, оператор continue
заставляет программу вернуться к началу цикла (что увеличивает значение i
). Если же делится, значение выводится на экран.Вторая часть программы демонстрирует работу с бесконечным циклом. Однако внутри этого цикла есть оператор 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(;;)` как эквивалентные конструкции. Поэтому выбор между ними зависит от личных предпочтений программиста.
(1) Зловещий `goto`
Ключевое слово `goto` существует уже давно во многих языках программирования. На самом деле, `goto` является первым способом управления потоком выполнения в ассемблере: «Если условие A, переходите сюда; иначе переходите туда». При чтении ассемблерного кода, генерируемого практически любыми компиляторами, можно заметить множество переходов. Однако `goto` представляет собой переход на уровне исходного кода, поэтому он приобрёл плохую славу. Если программа постоянно прыгает из одного места в другое, как можно понять порядок выполнения кода? После публикации знаменитой статьи Эдсгером Дейкстрой «Goto — зло», этот оператор стал менее популярен. На самом деле, проблема заключается не в использовании `goto`, а в его злоупотреблении. В некоторых редких случаях `goto` является лучшим способом организации потока управления.Несмотря на то что `goto` остаётся зарезервированным словом в Java, он официально не используется в языке; Java не имеет `goto`. Однако можно заметить отголоски `goto` в ключевых словах `break` и `continue`. Это не однонаправленное перемещение, а прерывание циклического оператора. Эти ключевые слова рассматриваются как часть проблемы `goto`, поскольку они используют ту же механику: метки.Метка — это идентификатор, следом за которым следует двоеточие, как показано ниже:
метка1:
Для Java единственным местом использования меток является перед циклом. Далее говорится, что она должна непосредственно предшествовать циклу — вставка любого оператора между меткой и циклом будет некорректной. Единственной причиной установки метки перед циклом является желание вложить другой цикл или оператор `switch` внутрь него. Это связано с тем, что ключевые слова `break` и `continue` обычно прерывают текущий цикл, но если они используются вместе с метками, они прерывают до места, где находится метка. Например:
метка1: внешний цикл { внутренний цикл { // ... break; // 1 // ... continue; // 2 // ... continue метка1; // 3 // ... break метка1; // 4 } }
В случае 1, `break` прерывает внутренний цикл и завершает внешний цикл. В случае 2, `continue` возвращает внутренний цикл к началу. Но в случае 3, `continue метка1` прерывает как внутренний, так и внешний циклы и переходит к метке `метка1`. После этого он продолжает выполнение цикла, начиная с внешнего цикла. В случае 4, `break метка1` также прерывает все циклы и возвращается к метке `метка1`, но не начинает новый цикл. То есть фактически полностью прекращаются два цикла.
Вот пример цикла `for`:
//: НазначенныеФоры.java // "назначенный for" в Java
## Пример программы с метками циклов```java
public class LabeledFor {
public static void main(String[] args) {
int i = 0;
outer: // Здесь нельзя иметь операторов
for (; true;) { // бесконечный цикл
inner: // Здесь нельзя иметь операторов
for (; i < 10; i++) {
prt("i = " + i);
if (i == 2) {
prt("continue");
continue;
}
if (i == 3) {
prt("break");
i++; // В противном случае i никогда
// не будет увеличиваться.
break;
}
if (i == 7) {
prt("continue outer");
i++; // В противном случае i никогда
// не будет увеличиваться.
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;
}
}
}
}
// Здесь нельзя прерывать или продолжать
// до меток
}
static void prt(String s) {
System.out.println(s);
}
}
///:~
Приведенная выше программа демонстрирует использование меток в Java для управления циклами. Внутренние и внешние циклы могут быть прерваны (break
) или продолжены (continue
) с использованием меток.
outer
: Указывает на внешний бесконечный цикл.inner
: Указывает на внутренний цикл внутри внешнего цикла.prt
: Выводит строки на консоль.continue outer
позволяет продолжить выполнение внешнего цикла.break outer
позволяет выйти из внешнего цикла.Этот пример показывает, как использовать метки для управления несколькими уровнями циклов в Java.
В данном примере используется метод `prt()`, который был определён в других примерах.
Обратите внимание, что break
прерывает цикл for
, и увеличивающее выражение не будет выполнено до конца цикла for
. Поскольку break
пропускает увеличивающее выражение, то при i==3
увеличение произойдет сразу. Аналогично, при i==7
выполнится continue outer
, что также приведет к пропуску увеличивающего выражения.Вот вывод программы:
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
:
//: LabeledWhile.java
// Цикл "помеченный while" в Java
public class LabeledWhile {
public static void main(String[] args) {
int i = 0;
outer:
while (true) {
prt("Внешний цикл while");
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
прервет текущий цикл и завершит выполнение цикла, указанного данной меткой.
Результат работы данного метода очевиден:
Внешний цикл while
i = 1
continue
i = 2
i = 3
continue outer
Внешний цикл while
i = 4
i = 5
break
Внешний цикл while
i = 6
i = 7
break outer
Основные моменты, которые следует запомнить: в Java использование меток требуется только при наличии вложенных циклов, когда вы хотите прервать или продолжить выполнение нескольких уровней вложенности.
В статье Дейкстры "Недостатки goto" он наиболее противился использованию меток, а не самому ключевому слову goto
. По мере увеличения количества меток в программе, он заметил, что вероятность возникновения ошибок также возрастает. Метки и goto
усложняют статический анализ программы за счет того, что они вводят множество "возвратов" в процессе выполнения программы. К счастью, метки в Java не создают таких проблем, так как их действие ограничено, и контроль выполнения программы не может быть передан произвольным образом.Это также вызывает интересный вопрос: ограничив возможности конструкций, можно сделать язык более полезным.
Конструкция Выбор
(Switch
) иногда рассматривается как форма выборочной инструкции. На основе значения целочисленного выражения, инструкция switch
выбирает одну из последовательностей кода для выполнения. Её формат следующий:
switch(целочисленный_выборочный_фактор) {
case значение1: инструкция; break;
case значение2: инструкция; break;
case значение3: инструкция; break;
case значение4: инструкция; break;
case значение5: инструкция; break;
//...
default: инструкция;
}
Здесь "целочисленный выборочный фактор" представляет собой специальное выражение, которое генерирует целое число. Конструкция switch
сравнивает результат этого выражения со всеми указанными значениями. При совпадении выполняется соответствующая инструкция (простая или составная). В случае отсутствия совпадений выполняется инструкция по умолчанию.В этом определении каждый case
завершается инструкцией break
, что позволяет перейти к концу блока switch
. Это традиционный способ использования switch
, но break
является необязательной частью. Без break
выполнение продолжится до следующего case
или до встречи break
. Хотя обычно это не желаемый эффект, опытные программисты могут использовать его эффективно. Обратите внимание, что инструкция default
не имеет break
, поскольку она находится уже после точки перехода. Конечно, если учитывать вопросы стиля программирования, можно добавить break
в конце инструкции default
, хотя это не будет иметь практического значения. Инструкция switch
является простым способом реализации множественного выбора (например, выбор одного пути из нескольких вариантов). Однако она требует использования выборочного фактора, который должен быть целочисленным значением типа int
или char
. Например, если попытаться использовать строку или число с плавающей запятой в качестве выборочного фактора, то они не будут работать в инструкции switch
. Для типов данных, отличных от целых чисел, следует использовать последовательность инструкций if
.
Приведённый ниже пример случайным образом генерирует буквы и проверяет, являются ли они гласными или согласными буквами:
Пример случайного генератора букв:
import random
def generate_letter():
return chr(random.randint(97, 122))
letter = generate_letter()
if letter in 'aeiou':
print(f'{letter} - Гласная')
elif letter in 'bcdfghjklmnpqrstvwxyz':
print(f'{letter} - Согласная')
else:
print('Неизвестная буква')
``````markdown
//: VowelsAndConsonants.java
// Демонстрация оператора switch
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("гласная");
break;
case 'y':
case 'w':
System.out.println("иногда гласная");
break;
default:
System.out.println("согласная");
}
}
}
} ///:~
Используя `Math.random()`, который возвращает значение от 0 до 1, можно получить случайное число от 0 до 26, добавив смещение `'a'`. Это позволяет нам получать случайные буквы от 'a' до 'z'.
Обратите внимание, что хотя мы работаем с символами, `switch` использует целочисленное представление символов. Внутри `case` используются заключённые в одиночные кавычки символы, которые также преобразуются в целое число для сравнения.
Заметьте, как `case` объединены друг с другом, образуя последовательность, которая обеспечивает несколько вариантов совпадения для конкретного участка кода. Также обратите внимание на то, что `break` помещается в конце каждого `case`, чтобы прекратить выполнение остальных `case`.
(1) Конкретные вычисления
Особенно важно обратить внимание на следующую строку:
```java
char c = (char)(Math.random() * 26 + 'a');
```Функция Math.random()
возвращает значение типа `double`, поэтому число 26 будет преобразовано в тип `double` для выполнения умножения. Умножение также вернет значение типа `double`. Для выполнения сложения необходимо преобразовать символ `'a'` в тип `double`. Преобразование значения типа `double` в тип `char` происходит автоматически.## Пример приведения типов чисел
public class CastingNumbers {
public static void main(String[] args) {
double
выше = 0.7,
ниже = 0.4;
System.out.println("выше: " + выше);
System.out.println("ниже: " + ниже);
System.out.println("(int)выше: " + (int)выше);
System.out.println("(int)ниже: " + (int)ниже);
System.out.println("(char)('a' + выше): " + (char)('a' + выше));
System.out.println("(char)('a' + ниже): " + (char)('a' + ниже));
}
}
///
выше: 0.7
ниже: 0.4
(int)выше: 0
(int)ниже: 0
(char)('a' + выше): a
(char)('a' + ниже): a
Поэтому ответ заключается в том, что при преобразовании значения типа float
или double
в целое число, десятичные части просто отбрасываются, а округление до ближайшего целого числа не производится.
Второй вопрос связан с методом Math.random()
. Он генерирует значение между 0 и 1, но включает ли он само значение 1? В строгой математической терминологии это может быть представлено как (0,1)
, [0,1]
, (0,1]
, или [0,1)
(квадратные скобки указывают на включение, круглые скобки — на исключение). Аналогичным образом, демонстрационная программа раскрывает этот вопрос:```
//: RandomBounds.java
// Generates whether Math.random() produces values of 0.0 and 1.0?
public class RandomBounds { static void usage() { System.err.println("Usage:\n\t" + "RandomBounds lower bound\n\t" + "RandomBounds upper bound"); System.exit(1); } public static void main(String[] args) { if(args.length != 1) usage(); if(args[0].equals("lower bound")) { while(Math.random() != 0.0) ; // Continue trying System.out.println("Generated 0.0!"); } else if(args[0].equals("upper bound")) { while(Math.random() != 1.0) ; // Continue trying System.out.println("Generated 1.0!"); } else usage(); } } ///:~
To run this program, simply enter the following commands in the command line:
java RandomBounds lower bound
or
java RandomBounds upper bound
In both cases, you will need to manually interrupt the execution of the program to notice that `Math.random()` seems never to generate values of 0.0 or 1.0. However, this is just an experiment. Given that there are more than 2^128 different double numbers between 0 and 1, the time required to generate all these values would be much longer than a human lifetime. Nevertheless, the final result shows that the range of values generated by `Math.random()` includes 0.0. In other words, the value range is [0,1).
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )