1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
3.2 执行控制.md 37 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

3.2 Выполнение контроля

3.2 Выполнение контроля

Java использует все конструкции управления, присутствующие в C, поэтому если вы ранее программировали на C или C++, большинство из этих конструкций должно быть вам знакомым. Большинство процедурных языков программирования предоставляют некоторые формы конструкций управления, которые обычно являются общими для всех языков. В Java ключевые слова включают if-else, while, do-while, for и выборочное выражение switch. Однако Java не поддерживает очень опасное goto (хотя это может быть временным решением для некоторых специфических проблем). Тем не менее можно выполнять переходы, аналогичные goto, но они гораздо более ограничены.

3.2.1 Истина и ложьВсе условные выражения используют истинность или ложность условия для определения потока выполнения. Пример условного выражения — это 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;
}

Лучше всего организовать конструкции управления потоком выполнения с отступами, чтобы читатели могли легко определить начало и конец каждого блока.

1. 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 не создают таких проблем, так как их действие ограничено, и контроль выполнения программы не может быть передан произвольным образом.Это также вызывает интересный вопрос: ограничив возможности конструкций, можно сделать язык более полезным.

3.2.7 Выбор

Конструкция Выбор (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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master