Skip to content

Latest commit

 

History

History
1258 lines (1189 loc) · 75.7 KB

c5.md

File metadata and controls

1258 lines (1189 loc) · 75.7 KB

ГЛАВА 5. Управляющие операторы

В этой главе речь пойдет об операторах, управляющих ходом выполнения программы на С#. Управляющие операторы разделяются на три категории: операторы выбора, к числу которых относятся операторы if и switch, итерационные операторы, в том числе операторы цикла for, while, do-while и foreach, а также операторы пере­ хода: break, continue, goto, return и throw. За исклю­ чением оператора throw, который является неотъемлемой частью встроенного в C# механизма обработки исключи­ тельных ситуаций, рассматриваемого в главе 13, все осталь­ ные управляющие операторы представлены в этой главе.

Оператор if

Оператор if уже был представлен в главе 2, а здесь он рассматривается более подробно. Ниже приведена полная форма этого оператора:

if(условие) оператор;
else оператор;

где условие — это некоторое условное выражение, a опe- ратор — адресат операторов if и else. Оператор else не является обязательным. Адресатом обоих операторов, if и else, могут также служить блоки операторов. Ниже приведена общая форма оператора if, в котором исполь­ зуются блоки операторов.

if(условие)
{
последовательность операторов
}
else
{
последовательность операторов
}

Если условное выражение оказывается истинным, то выполняется адресат операто­ ра if. В противном случае выполняется адресат оператора else, если таковой суще­ ствует. Но одновременно не может выполняться и то и другое. Условное выражение, управляющее оператором if, должно давать результат типа bool.

Ниже приведен пример простой программы, в которой операторы if и else используются для того, чтобы сообщить, является ли число положительным или отрицательным.

// Определить, является ли числовое значение положительным или
отрицательным.
using System;

class PosNeg {
    static void Main() {
        int i;
        for(i=-5; i <= 5; i++) {
            Console.Write("Проверка " + i + ");
            if(i < 0) Console.WriteLine("отрицательное число”);
            else Console.WriteLine("положительное число");
        }
    }
}

Результат выполнения этой программы выглядит следующим образом.

Проверка -5: отрицательное число
Проверка -4: отрицательное число
Проверка -3: отрицательное число
Проверка -2: отрицательное число
Проверка -1: отрицательное число
Проверка 0: положительное число
Проверка 1: положительное число
Проверка 2: положительное число
Проверка 3: положительное число
Проверка 4: положительное число
Проверка 5: положительное число

Если в данном примере значение переменной i оказывается меньше нуля, то вы­ полнятся адресат оператора if. В противном случае выполняется адресат оператора else, одновременно они не выполняются.

Вложенные операторы if

Вложенным называется такой оператор if, который является адресатом другого оператора if или же оператора else. Вложенные операторы if очень часто приме­ няются в программировании. Что же касается их применения в С#, то не следует забы­ вать, что любой оператор else всегда связан с ближайшим оператором if, т.е. с тем оператором if, который находится в том же самом блоке, где и оператор else, но не с другим оператором else. Рассмотрим следующий пример.

if (i == 10) {
    if(j < 20) a = b;
    if(k > 100) с = d;
    else a = с; // этот оператор else связан с оператором if(k > 100)
}
else a = d; // этот оператор else связан с оператором if(i == 10)

Как следует из комментариев к приведенному выше фрагменту кода, последний оператор else не связан с оператором if(j < 20), поскольку они не находятся в одном и том же блоке, несмотря на то, что этот оператор является для него ближай­ шим оператором if без вспомогательного оператора else. Напротив, последний опе­ ратор else связан с оператором if(i == 10). А внутренний оператор else связан с оператором if(k > 100), поскольку этот последний является для него ближайшим оператором if в том же самом блоке.

В приведенном ниже примере программы демонстрируется применение вложен­ ного оператора if. В представленной ранее программе определения положительных и отрицательных чисел о нуле сообщалось как о положительном числе. Но, как прави­ ло, нуль считается числом, не имеющим знака. Поэтому в следующей версии данной программы о нуле сообщается как о числе, которое не является ни положительным, ни отрицательным.

// Определить, является ли числовое значение
// положительным, отрицательным или нулевым.
using System;

class PosNegZero {
    static void Main() {
        int i;
        for(i=-5; i <= 5; i++) {
            Console.Write("Проверка " + i + ": ");
            if(i < 0) Console.WriteLine("отрицательное число");
            else if(i == 0) Console.WriteLine("число без знака");
            else Console.WriteLine ("положительное число");
        }
    }
}

Ниже приведен результат выполнения этой программы.

Проверка -5: отрицательное число
Проверка -4: отрицательное число
Проверка -3: отрицательное число
Проверка -2: отрицательное число
Проверка -1: отрицательное число
Проверка 0: число без знака
Проверка 1: положительное число
Проверка 2: положительное число
Проверка 3: положительное число
Проверка 4: положительное число
Проверка 5: положительное число

Конструкция if-else-if

В программировании часто применяется многоступенчатая конструкция if-elseif, состоящая из вложенных операторов if. Ниже приведена ее общая форма.

if(условие)
оператор;
else if (условие)
оператор;
else if (условие)
оператор;
else
оператор;

Условные выражения в такой конструкции вычисляются сверху вниз. Как только обнаружится истинное условие, выполняется связанный с ним оператор, а все осталь­ ные операторы в многоступенчатой конструкции опускаются.

Если ни одно из условий не является истинным, то выполняется последний опера­ тор else, который зачастую служит в качестве условия, устанавливаемого по умол­ чанию. Когда же последний оператор else отсутствует, а все остальные проверки по условию дают ложный результат, то никаких действий вообще не выполняется.

В приведенном ниже примере программы демонстрируется применение многосту­ пенчатой конструкции if-else-if. В этой программе обнаруживается наименьший множитель заданного целого значения, состоящий из одной цифры.

// Определить наименьший множитель заданного
// целого значения, состоящий из одной цифры.
using System;

class Ladder {
    static void Main() {
        int num;
        for(num = 2; num < 12; num++) {
            if((num % 2) == 0)
                Console.WriteLine("Наименьший множитель числа " + num + " равен 2.");
            else if((num % 3) == 0)
                Console.WriteLine("Наименьший множитель числа " + num + " равен 3.");
            else if((num % 5) == 0)
                Console.WriteLine("Наименьший множитель числа " + num + " равен 5.");
            else if((num % 7) == 0)
                Console.WriteLine("Наименьший множитель числа " + num + " равен 7.");
            else
                Console.WriteLine(num + " не делится на 2, 3, 5 или 7.");
        }
    }
}

Вот к какому результату приводит выполнение этой программы.

Наименьший множитель числа 2 равен 2
Наименьший множитель числа 3 равен 3
.
.
.
Наименьший множитель числа 4 равен 2
Наименьший множитель числа 5 равен 5
Наименьший множитель числа 6 равен 2
Наименьший множитель числа 7 равен 7
Наименьший множитель числа 8 равен 2
Наименьший множитель числа 9 равен 3
Наименьший множитель числа 10 равен 2
11 не делится на 2, 3, 5 или 7.

Как видите, последний оператор else выполняется лишь в том случае, если не удается выполнить ни один из предыдущих операторов.

Оператор switch

Вторым оператором выбора в C# является оператор switch, который обеспечивает многонаправленное ветвление программы. Следовательно, этот оператор позволяет сделать выбор среди нескольких альтернативных вариантов дальнейшего выполнения программы. Несмотря на то что многонаправленная проверка может быть организо­ вана с помощью последовательного ряда вложенных операторов if, во многих слу­ чаях более эффективным оказывается применение оператора switch. Этот оператор действует следующим образом. Значение выражения последовательно сравнивается с константами выбора из заданного списка. Как только будет обнаружено совпадение с одним из условий выбора, выполняется последовательность операторов, связанных с этим условием. Ниже приведена общая форма оператора switch.

switch(выражение) {
    case константа1:
        последовательность операторов
        break;
    case константа2:
        последовательность операторов
        break;
    case константа3:
        последовательность операторов
        break;
    default:
        последовательность операторов
        break;
}

Заданное выражение в операторе switch должно быть целочисленного типа (char, byte, short или int), перечислимого или же строкового. (О перечислениях и сим­ вольных строках типа string речь пойдет далее в этой книге.) А выражения других типов, например с плавающей точкой, в операторе switch не допускаются. Зачастую выражение, управляющее оператором switch, просто сводится к одной переменной. Кроме того, константы выбора должны иметь тип, совместимый с типом выражения. В одном операторе switch не допускается наличие двух одинаковых по значению констант выбора.

Последовательность операторов из ветви default выполняется в том случае, если ни одна из констант выбора не совпадает с заданным выражением. Ветвь default не является обязательной. Если же она отсутствует и выражение не совпадает ни с одним из условий выбора, то никаких действий вообще не выполняется. Если же происходит совпадение с одним из условий выбора, то выполняются операторы, связанные с этим условием, вплоть до оператора break.

Ниже приведен пример программы, в котором демонстрируется применение опе­ ратора switch.

// Продемонстрировать применение оператора switch.
using System;
class SwitchDemo {
    static void Main() {
        int i;
        for(i=0; i<10; i++)
        switch(i) {
            case 0:
                Console.WriteLine("i равно нулю");
                break;
            case 1:
                Console.WriteLine("i равно единице");
                break;
            case 2:
                Console.WriteLine("i равно двум");
                break;
            case 3:
                Console.WriteLine("i равно трем");
                break;
            case 4:
                Console.WriteLine("i равно четырем");
                break;
            default:
                Console.WriteLine("i равно или больше пяти");
                break;
        }
    }
}

Результат выполнения этой программы выглядит следующим образом.

i равно нулю.
i равно единице.
i равно двум.
i равно трем.
i равно четырем.
i равно или больше пяти.
i равно или больше пяти.
i равно или больше пяти.
i равно или больше пяти.
i равно или больше пяти.

Как видите, на каждом шаге цикла выполняются операторы, связанные с совпа­ дающей константой выбора, в обход всех остальных операторов. Когда же значение переменной i становится равным или больше пяти, то оно не совпадает ни с одной из констант выбора, а следовательно, выполняются операторы из ветви default.

В приведенном выше примере оператором switch управляла переменная i типа int. Как пояснялось ранее, для управления оператором switch может быть использо­ вано выражение любого целочисленного типа, включая и char. Ниже приведен при­ мер применения выражения и констант выбора типа char в операторе switch.

// Использовать элементы типа char для управления оператором switch.
using System;

class SwitchDemo2 {
    static void Main() {
        char ch;
        for(ch='A'; ch<= 'E'; ch++)
        switch(ch) {
            case 'A':
                Console.WriteLine("ch содержит A");
                break;
            case 'В':
                Console.WriteLine("ch содержит В");
                break;
            case 'С':
                Console.WriteLine("ch содержит С");
                break;
            case 'D':
                Console.WriteLine("ch содержит D");
                break;
            case 'E':
                Console.WriteLine("ch содержит E");
                break;
        }
    }
}

Вот какой результат дает выполнение этой программы.

ch содержит А
ch содержит В
ch содержит С
ch содержит D
ch содержит Е

Обратите в данном примере внимание на отсутствие ветви default в операторе switch. Напомним, что ветвь default не является обязательной. Когда она не нужна, ее можно просто опустить.

Переход последовательности операторов, связанных с одной ветвью case, в сле­ дующую ветвь case считается ошибкой, поскольку в С# должно непременно соблю­ даться правило недопущения "провалов" в передаче управления ходом выполнения программы. Именно поэтому последовательность операторов в каждой ветви case оператора switch оканчивается оператором break. (Избежать подобных "провалов", можно также с помощью оператора безусловного перехода goto, рассматриваемого далее в этой главе, но для данной цели чаще применяется оператор break.) Когда в последовательности операторов отдельной ветви case встречается оператор break, происходит выход не только из этой ветви, но из всего оператора switch, а выполне­ ние программы возобновляется со следующего оператора, находящегося за предела­ ми оператора switch. Последовательность операторов в ветви default также должна быть лишена "провалов", поэтому она завершается, как правило, оператором break.

Правило недопущения "провалов" относится к тем особенностям языка С#, кото­ рыми он отличается от С, C++ и Java. В этих языках программирования одна ветвь case может переходить (т.е. "проваливаться") в другую. Данное правило установлено в C# для ветвей case по двум причинам. Во-первых, оно дает компилятору возмож­ ность свободно изменять порядок следования последовательностей операторов из вет­ вей case для целей оптимизации. Такая реорганизация была бы невозможной, если бы одна ветвь case могла переходить в другую. И во-вторых, требование завершать каждую ветвь case явным образом исключает непроизвольные ошибки программи­ рования, допускающие переход одной ветви case в другую.

Несмотря на то что правило недопущения "провалов" не допускает переход одной ветви case в другую, в двух или более ветвях case все же разрешается ссылаться с по­ мощью меток на одну и ту же кодовую последовательность, как показано в следующем примере программы.

// Пример "проваливания" пустых ветвей case.
using System;

class EmptyCasesCanFall {
    static void Main() {
        int i;
        for(i=1; i < 5; i++)
        switch(i) {
            case 1:
            case 2:
            case 3: Console.WriteLine("i равно 1, 2 или 3");
                break;
            case 4: Console.WriteLine("i равно 4");
                break;
        }
    }
}

Ниже приведен результат выполнения этой программы.

i равно 1, 2 или 3
i равно 1, 2 или 3
i равно 1, 2 или 3
i равно 4

Если значение переменной i в данном примере равно 1, 2 иди 3, то выполняется пер­ вый оператор, содержащий вызов метода WriteLine(). Такое расположение несколь­ ких меток ветвей case подряд не нарушает правило недопущения "провалов"; посколь­ ку во всех этих ветвях используется одна и та же последовательность операторов.

Расположение нескольких меток ветвей case подряд зачастую применяется в том случае, если у нескольких ветвей имеется общий код. Благодаря этому исключается излишнее дублирование кодовых последовательностей.

Вложенные операторы switch

Один оператор switch может быть частью последовательности операторов дру­ гого, внешнего оператора switch. И такой оператор switch называется вложенным. Константы выбора внутреннего и внешнего операторов switch могут содержать об­ щие значения, не вызывая никаких конфликтов. Например, следующий фрагмент кода является вполне допустимым.

switch(ch1) {
    case 'A': Console.WriteLine("Эта ветвь А — Часть " +
                            "внешнего оператора switch.");
    switch(ch2) {
        case 'A':
            Console.WriteLine("Эта ветвь A — часть " +
                            "внутреннего оператора switch");
            break;
        case 'В': // ...
    } // конец внутреннего оператора switch
    break;
    case 'В': // ...

Оператор цикла for

Оператор for уже был представлен в главе 2, а здесь он рассматривается более под­ робно. Вас должны приятно удивить эффективность и гибкость этого оператора. Пре­ жде всего, обратимся к самым основным и традиционным формам оператора for. Ниже приведена общая форма оператора for для повторного выполнения един­ ственного оператора.

for(инициализация; условие; итерация) оператор;

А вот как выглядит его форма для повторного выполнения кодового блока:

for(инициализация; условие; итерация)
{
последовательность операторов;
}

где инициализация, как правило, представлена оператором присваивания, задающим первоначальное значение переменной, которая выполняет роль счетчика и управляет циклом; условие — это логическое выражение, определяющее необходимость повто­ рения цикла; а итерация — выражение, определяющее величину, на которую должно изменяться значение переменной, управляющей циклом, при каждом повторе цикла. Обратите внимание на то, что эти три основные части оператора цикла for должны быть разделены точкой с запятой. Выполнение цикла for будет продолжаться до тех пор, пока проверка условия дает истинный результат. Как только эта проверка даст ложный результат, цикл завершится, а выполнение программы будет продолжено с оператора, следующего после цикла for.

Цикл for может продолжаться как в положительном, так и в отрицательном на­ правлении, изменяя значение переменной управления циклом на любую величину. В приведенном ниже примере программы выводятся числа постепенно уменьшаю­ щиеся от 100 до -100 на величину 5.

// Выполнение цикла for в отрицательном направлении.
using System;

class DecrFor {
    static void Main() {
        int x;
        for(x = 100; x > -100; x -= 5)
            Console.WriteLine(x);
    }
}

В отношении циклов for следует особо подчеркнуть, что условное выражение всег­ да проверяется в самом начале цикла. Это означает, что код в цикле может вообще не выполняться, если проверяемое условие с самого начала оказывается ложным. Рассмо­ трим следующий пример.

for(count=10; count < 5; count++)
x += count; // этот оператор не будет выполняться

Данный цикл вообще не будет выполняться, поскольку первоначальное значение переменной count, которая им управляет, сразу же оказывается больше 5. Это означа­ ет, что условное выражение count < 5 оказывается ложным с самого начала, т.е. еще до выполнения первого шага цикла.

Оператор цикла for — наиболее полезный для повторного выполнения операций известное число раз. В следующем примере программы используются два цикла for для выявления простых чисел в пределах от 2 до 20. Если число оказывается непро­ стым, то выводится наибольший его множитель.

// Выяснить, является ли число простым. Если оно
// непростое, вывести наибольший его множитель.
using System;

class FindPrimes {
    static void Main() {
        int num;
        int i;
        int factor;
        bool isprime;
        for(num = 2; num < 20; num++) {
            isprime = true;
            factor = 0;
            // Выяснить, делится ли значение переменной num нацело.
            for(i=2; i <= num/2; i++) {
                if((num % i) == 0) {
                    // Значение переменной num делится нацело.
                    // Следовательно, это непростое число.
                    isprime = false;
                    factor = i;
                }
            }
            if(isprime)
                Console.WriteLine(num + " — простое число.");
            else
                Console.WriteLine("Наибольший множитель числа " + num +
                                " равен " + factor);
        }
    }
}

Ниже приведен результат выполнения этой программы.

2 — простое число
3 — простое число
Наибольший множитель числа 4 равен 2
5 — простое число
Наибольший множитель числа 6 равен 3
7 — простое число
Наибольший множитель числа 8 равен 4
Наибольший множитель числа 9 равен 3
Наибольший множитель числа 10 равен 5
11 — простое число
Наибольший множитель числа 12 равен 6
13 — простое число
Наибольший множитель числа 14 равен 7
Наибольший множитель числа 15 равен 5
Наибольший множитель числа 16 равен 8
17 — простое число
Наибольший множитель числа 18 равен 9
19 - простое число

Некоторые разновидности оператора цикла for

Оператор цикла for относится к самым универсальным операторам языка С#, по­ скольку он допускает самые разные варианты своего применения. Некоторые разно­ видности оператора цикла for рассматриваются ниже.

Применение нескольких переменных управления циклом

В операторе цикла for разрешается использовать две или более переменных для управления циклом. В этом случае операторы инициализации и инкремента каждой переменной разделяются запятой. Рассмотрим следующий пример программы.

// Использовать запятые в операторе цикла for.
using System;

class Comma {
    static void Main() {
        int i, j;
        for(i=0, j=10; i < j; i++, j--)
           Console.WriteLine("i и j: " + i + " " + j);
    }
}

Выполнение этой программы дает следующий результат.

i и j: 0 10
i и j: 1 9
i и j: 2 8
i и j: 3 7
i и j: 4 6

В данном примере запятыми разделяются два оператора инициализации и еще два итерационных выражения. Когда цикл начинается, инициализируются обе пере­ менные, i и j. Всякий раз, когда цикл повторяется, переменная i инкрементируется, а переменная j декрементируется. Применение нескольких переменных управления циклом нередко оказывается удобным, упрощая некоторые алгоритмы. Теоретически в операторе цикла for может присутствовать любое количество операторов иници­ ализации и итерации, но на практике цикл получается слишком громоздким, если применяется более двух подобных операторов.

Ниже приведен практический пример применения нескольких переменных управления циклом в операторе for. В этом примере программы используются две переменные управления одним циклом for для выявления наибольшего и наи­ меньшего множителя целого числа (в данном случае — 100). Обратите особое вни­ мание на условие окончания цикла. Оно опирается на обе переменные управления циклом.

// Использовать запятые в операторе цикла for для
// выявления наименьшего и наибольшего множителя числа.
using System;

class Comma {
    static void Main() {
        int i, j;
        int smallest, largest;
        int num;
        num = 100;
        smallest = largest = 1;
        for(i=2, j=num/2; (i <= num/2) & (j >= 2); i++, j--) {
            if((smallest == 1) & ((num % i) == 0))
                smallest = i;
            if((largest == 1) & ((num % j) == 0))
                largest = j;
        }
        Console.WriteLine("Наибольший множитель: " + largest);
        Console.WriteLine("Наименьший множитель: " + smallest);
    }
}

Ниже приведен результат выполнения этой программы.

Наибольший множитель: 50
Наименьший множитель: 2

Благодаря применению двух переменных управления циклом удается выявить наи­ меньший и наибольший множители числа в одном цикле for. В частности, управляю­ щая переменная i служит для выявления наименьшего множителя. Первоначально ее значение устанавливается равным 2 и затем инкрементируется до тех пор, пока не пре­ высит половину значения переменной num. А управляющая переменная j служит для выявления наибольшего множителя. Ее значение первоначально устанавливается рав­ ным половине значения переменной num и затем декрементируется до тех пор, пока не станет меньше 2. Цикл продолжает выполняться до тех пор, пока обе переменные, i и j, не достигнут своих конечных значений. По завершении цикла оба множителя оказываются выявленными.

Условное выражение

Условным выражением, управляющим циклом for, может быть любое действи­ тельное выражение, дающее результат типа bool. В него не обязательно должна вхо­ дить переменная управления циклом. В следующем примере программы управление циклом for осуществляется с помощью значения переменной done.

// Условием выполнения цикла может служить любое выражение типа bool.
using System;
class forDemo {
    static void Main() {
        int i, j;
        bool done = false;
        for(i=0, j=100; !done; i++, j--) {
            if(i*i >= j) done = true;
            Console.WriteLine("i, j: " + i + " " + j);
        }
    }
}

Ниже приведен результат выполнения этой программы.

i, j: 0 100
i, j: 1 99
i, j: 2 98
i, j: 3 97
i, j: 4 96
i, j: 5 95
i, j: 6 94
i, j: 7 93
i, j: 8 92
i, j: 9 91
i, j: 10 90

В данном примере цикл for повторяется до тех пор, пока значение переменной done типа не окажется истинным (true). Истинное значение переменной done уста­ навливается в цикле, когда квадрат значения переменной i оказывается больше или равным значению переменной j.

Отсутствующие части цикла

Ряд интересных разновидностей цикла for получается в том случае, если оставить пустыми отдельные части определения цикла. В C# допускается оставлять пустыми любые или же все части инициализации, условия и итерации в операторе цикла for. В качестве примера рассмотрим такую программу.

// Отдельные части цикла for могут оставаться пустыми.
using System;

class Empty {
    static void Main() {
        int i;
        for(i = 0; i < 10; ) {
            Console.WriteLine("Проход №" + i);
            i++; // инкрементировать переменную управления циклом
        }
    }
}

В данном примере итерационное выражение в определении цикла for оказывается пустым, т.е. оно вообще отсутствует. Вместо этого переменная i, управляющая ци­ клом, инкрементируется в теле самого цикла. Это означает, что всякий раз, когда цикл повторяется, значение переменной i проверяется на равенство числу 10, но никаких других действий при этом не происходит. А поскольку переменная i инкрементиру­ ется в теле цикла, то сам цикл выполняется обычным образом, выводя приведенный ниже результат.

Проход №0
Проход №1
Проход №2
Проход №3
Проход №4
Проход №5
Проход №6
Проход №7
Проход №8
Проход №9

В следующем примере программы из определения цикла for исключена инициа­ лизирующая часть.

// Исключить еще одну часть из определения цикла for.
using System;

class Empty2 {
    static void Main() {
        int i;
        i = 0; // исключить инициализацию из определения цикла
        for(; i < 10; ) {
            Console.WriteLine("Проход №" + i);
            i++; // инкрементировать переменную управления циклом
        }
    }
}

В данном примере переменная i инициализируется перед началом цикла, а не в са­ мом цикле for. Как правило, переменная управления циклом инициализируется в ци­ кле for. Выведение инициализирующей части за пределы цикла обычно делается лишь в том случае, если первоначальное значение данной переменной получается в результате сложного процесса, который нецелесообразно вводить в операторе цикла for.

Бесконечный цикл

Если оставить пустым выражение условия в операторе цикла for, то получится бесконечный цикл, т.е. такой цикл, который никогда не заканчивается. В качестве при­ мера в следующем фрагменте кода показано, каким образом в С# обычно создается бесконечный цикл.

for(;;) // цикл, намеренно сделанный бесконечным
{
    //...
}

Этот цикл будет выполняться бесконечно. Несмотря на то что бесконечные циклы требуются для решения некоторых задач программирования, например при разра­ ботке командных процессоров операционных систем, большинство так называемых "бесконечных" циклов на самом деле представляет собой циклы со специальными тре­ бованиями к завершению. (Подробнее об этом — в разделе "Применение оператора break для выхода из цикла" далее в этой главе.)

Циклы без тела

В C# допускается оставлять пустым тело цикла for или любого другого цикла, по­ скольку пустой оператор с точки зрения синтаксиса этого языка считается действитель­ ным. Циклы без тела нередко оказываются полезными. Например, в следующей про­ грамме цикл без тела служит для получения суммы чисел от 1 до 5.

// Тело цикла может быть пустым.
using System;

class Empty3 {
    static void Main() {
        int i;
        int sum = 0;
        // получить сумму чисел от 1 до 5
        for(i = 1; i <= 5; sum += i++);
        Console.WriteLine("Сумма равна " + sum);
    }
}

Выполнение этой программы дает следующий результат.

Сумма равна 15

Обратите внимание на то, что процесс суммирования выполняется полностью в операторе цикла for, и для этого тело цикла не требуется. В этом цикле особое вни­ мание обращает на себя итерационное выражение.

sum += i++

Подобные операторы не должны вас смущать. Они часто встречаются в програм­ мах, профессионально написанных на С#, и становятся вполне понятными, если разо­ брать их по частям. Дословно приведенный выше оператор означает следующее: сло­ жить со значением переменной sum результат суммирования значений переменных sum и i, а затем инкрементировать значение переменной i. Следовательно, данный оператор равнозначен следующей последовательности операторов.

sum = sum + i;
i++;

Объявление управляющих переменных в цикле for

Нередко переменная, управляющая циклом for, требуется только для выполнения самого цикла и нигде больше не используется. В таком случае управляющую пере­ менную можно объявить в инициализирующей части оператора цикла for. Напри­ мер, в приведенной ниже программе вычисляется сумма и факториал чисел от 1 до 5, а переменная i, управляющая циклом for, объявляется в этом цикле.

// Объявить переменную управления циклом в самом цикле for.
using System;

class ForVar {
    static void Main() {
        int sum = 0;
        int fact = 1;
        // вычислить факториал чисел от 1 до 5
        for (int i = 1; i <= 5; i++) {
            sum += i; // Переменная i действует в цикле.
            fact *= i;
        }
        // А здесь переменная i недоступна.
        Console.WriteLine("Сумма равна " + sum);
        Console.WriteLine("Факториал равен " + fact);
    }
}

Объявляя переменную в цикле for, не следует забывать о том, что область действия этой переменной ограничивается пределами оператора цикла for. Это означает, что за пределами цикла действие данной переменной прекращается. Так, в приведенном выше примере переменная i оказывается недоступной за пределами цикла for. Для того чтобы использовать переменную управления циклом в каком-нибудь другом ме­ сте программы, ее нельзя объявлять в цикле for.

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

Оператор цикла while

Еще одним оператором цикла в C# является оператор while. Ниже приведена общая форма этого оператора:

while (условие) оператор;

где оператор — это единственный оператор или же блок операторов, а условие озна­ чает конкретное условие управления циклом и может быть любым логическим выра­ жением. В этом цикле оператор выполняется до тех пор, пока условие истинно. Как только условие становится ложным, управление программой передается строке кода, следующей непосредственно после цикла.

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

// Вычислить порядок величины целого числа.
using System;
class WhileDemo {
    static void Main() {
        int num;
        int mag;
        num = 435679;
        mag = 0;
        Console.WriteLine("Число: " + num);
        while(num > 0) {
            mag++;
            num = num / 10;
        };
        Console.WriteLine("Порядок величины: " + mag);
    }
}

Выполнение этой программы дает следующий результат.

Число: 435679
Порядок величины: 6

Приведенный выше цикл while действует следующим образом. Сначала проверя­ ется значение переменной num. Если оно больше нуля, то переменная mag, выполняю­ щая роль счетчика порядка величины, инкрементируется, а значение переменной num делится на 10. Цикл повторяется до тех пор, пока значение переменной num остается больше нуля. Как только оно окажется равным нулю, цикл завершается, а в перемен­ ной mag остается порядок величины первоначального числового значения.

Как и в цикле for, в цикле while проверяется условное выражение, указываемое в самом начале цикла. Это означает, что код в теле цикла может вообще не выполнять­ ся, а также избавляет от необходимости выполнять отдельную проверку перед самим циклом. Данное свойство цикла while демонстрируется в следующем примере про­ граммы, где вычисляются целые степени числа 2 от 0 до 9.

// Вычислить целые степени числа 2.
using System;

class Power {
    static void Main() {
        int e;
        int result;
        for(int i=0; i < 10; i++) {
            result = 1;
            e = i;
            while (e > 0) {
                result *= 2;
                e--;
            }
            Console.WriteLine("2 в степени " + i + " равно " + result);
        }
    }
}

Результат выполнения этой программы приведен ниже.

2 в степени 0 равно 1
2 в степени 1 равно 2
2 в степени 2 равно 4
2 в степени 3 равно 8
2 в степени 4 равно 16
2 в степени 5 равно 32
2 в степени 6 равно 64
2 в степени 7 равно 128
2 в степени 8 равно 256
2 в степени 9 равно 512

Обратите внимание на то, что цикл while выполняется только в том случае, если значение переменной е больше нуля. А когда оно равно нулю, как это имеет место на первом шаге цикла for, цикл while пропускается.

Оператор цикла do-while

Третьим оператором цикла в C# является оператор do-while. В отличие от опе­ раторов цикла for и while, в которых условие проверялось в самом начале цикла, в операторе do-while условие выполнения цикла проверяется в самом его конце. Это означает, что цикл do-while всегда выполняется хотя бы один раз. Ниже приведена общая форма оператора цикла do-while.

do {
операторы;
} while (условие);

При наличии лишь одного оператора фигурные скобки в данной форме записи необязательны. Тем не менее они зачастую используются для того, чтобы сделать кон­ струкцию do-while более удобочитаемой и не путать ее с конструкцией цикла while. Цикл do-while выполняется до тех пор, пока условное выражение истинно.

В приведенном ниже примере программы цикл do-while используется для пред­ ставления отдельных цифр целого числа в обратном порядке.

// Отобразить цифры целого числа в обратном порядке.
using System;

class DoWhileDemo {
    static void Main() {
        int num;
        int nextdigit;
        num = 198;
        Console.WriteLine("Число: " + num);
        Console.Write("Число в обратном порядке: ");
        do {
            nextdigit = num % 10;
            Console.Write(nextdigit);
            num = num / 10;
        } while(num > 0);
        Console.WriteLine();
    }
}

Выполнение этой программы дает следующий результат.

Число: 198
Число в обратном порядке: 891

Приведенный выше цикл действует следующим образом. На каждом его шаге крайняя слева цифра получается в результате расчета остатка от деления целого числа (значения переменной num) на 10. Полученная в итоге цифра отображается. Далее зна­ чение переменной num делится на 10. А поскольку это целочисленное деление, то в его результате крайняя слева цифра отбрасывается. Этот процесс повторяется до тех пор, пока значение переменной num не достигнет нуля.

Оператор цикла foreach

Оператор цикла foreach служит для циклического обращения к элементам кол­ лекции, которая представляет собой группу объектов. В C# определено несколько ви­ дов коллекций, к числу которых относится массив. Подробнее о цикле foreach речь пойдет в главе 7, где рассматриваются массивы.

Применение оператора break для выхода из цикла

С помощью оператора break можно специально организовать немедленный вы­ ход из цикла в обход любого кода, оставшегося в теле цикла, а также минуя проверку условия цикла. Когда в теле цикла встречается оператор break, цикл завершается, а выполнение программы возобновляется с оператора, следующего после этого цикла.

Рассмотрим простой пример программы.

// Применить оператор break для выхода из цикла.
using System;

class BreakDemo {
    static void Main() {
        // Использовать оператор break для выхода из этого цикла.
        for(int i=-10; i <= 10; i++) {
            if(i > 0) break; // завершить цикл, как только значение
            // переменной i станет положительным
            Console.Write(i + " ");
        }
        Console.WriteLine("Готово!");
    }
}

Выполнение этой программы дает следующий результат.

-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 Готово!

Как видите, цикл for организован для выполнения в пределах от -10 до 10, но, не­ смотря на это, оператор break прерывает его раньше, когда значение переменной i становится положительным.

Оператор break можно применять в любом цикле, предусмотренном в С#. В каче­ стве примера ниже приведена версия предыдущей программы, переделанная с целью использовать цикл do-while.

// Применить оператор break для выхода из цикла do-while.
using System;

class BreakDemo2 {
    static void Main() {
        int i;
        i = -10;
        do {
            if(i > 0) break;
            Console.Write(i + " ");
            i++;
        } while(i <= 10);
        Console.WriteLine("Готово!");
    }
}

А теперь рассмотрим более практический пример применения оператора break. В приведенной ниже программе выявляется наименьший множитель числа.

// Выявить наименьший множитель числа.
using System;

class FindSmallestFactor {
    static void Main() {
        int factor = 1;
        int num = -1000;
        for (int i=2; i <= num/i;' i++) {
            if((num%i) == 0) {
                factor = i;
                break; // прервать цикл, как только будет
                // выявлен наименьший множитель числа
            }
        }
        Console.WriteLine("Наименьший множитель равен " + factor);
    }
}

Результат выполнения этой программы выглядит следующим образом.

Наименьший множитель равен 2

Оператор break прерывает выполнение цикла for, как только будет выявлен наи­ меньший множитель числа. Благодаря такому применению оператора break исклю­ чается опробование любых других значений после выявления наименьшего множите­ ля числа, а следовательно, и неэффективное выполнение кода.

Если оператор break применяется в целом ряде вложенных циклов, то он преры­ вает выполнение только самого внутреннего цикла. В качестве примера рассмотрим следующую программу.

// Применить оператор break во вложенных циклах.
using System;

class BreakNested {
    static void Main() {
        for(int i=0; i<3; i++) {
            Console.WriteLine("Подсчет во внешнем цикле: " + i);
            Console.Write(" Подсчет во внутреннем цикле: ");
            int t = 0;
            while(t < 100) {
                if(t == 10) break; // прервать цикл, если t равно 10
                Console.Write(t + " ");
                t++;
            }
            Console.WriteLine();
        }
        Console.WriteLine("Циклы завершены.");
    }
}

Выполнение этой программы дает следующий результат.

Подсчет во внешнем цикле: 0
    Подсчет во внутреннем цикле: 0 1 2 3 4 5 6 7 8 9
Подсчет во внешнем цикле: 1
    Подсчет во внутреннем цикле: 0 1 2 3 4 5 6 1 8 9
Подсчет во внешнем цикле: 2
    Подсчет во внутреннем цикле: 0 1 2 3 4 5 6 1 8 9
Циклы завершены

Как видите, оператор break из внутреннего цикла вызывает прерывание только этого цикла, а на выполнение внешнего цикла он не оказывает никакого влияния.

В отношении оператора break необходимо также иметь в виду следующее. Во- первых, в теле цикле может присутствовать несколько операторов break, но приме­ нять их следует очень аккуратно, поскольку чрезмерное количество операторов break обычно приводит к нарушению нормальной структуры кода. И во-вторых, оператор break, выполняющий выход из оператора switch, оказывает воздействие только на этот оператор, но не на объемлющие его циклы.

Применение оператора continue

С помощью оператора continue можно организовать преждевременное заверше­ ние шага итерации цикла в обход обычной структуры управления циклом. Оператор continue осуществляет принудительный переход к следующему шагу цикла, пропу­ ская любой код, оставшийся невыполненным. Таким образом, оператор continue слу­ жит своего рода дополнением оператора break. В приведенном ниже примере про­ граммы оператор continue используется в качестве вспомогательного средства для вывода четных чисел в пределах от 0 до 100.

// Применить оператор continue.
using System;

class ContDemo {
    static void Main() {
        // вывести четные числа от 0 до 100.
        for (int i = 0; i <= 100; i++) {
            if((i%2) != 0) continue; // перейти к следующему шагу итерации
            Console.WriteLine(i);
        }
    }
}

В данном примере выводятся только четные числа, поскольку при обнаружении нечетного числа шаг итерации цикла завершается преждевременно в обход вызова ме­ тода WriteLine().

В циклах while и do-while оператор continue вызывает передачу управления не­ посредственно условному выражению, после чего продолжается процесс выполнения цикла. А в цикле for сначала вычисляется итерационное выражение, затем условное выражение, после чего цикл продолжается.

Оператор continue редко находит удачное применение, в частности, потому, что в C# предоставляется богатый набор операторов цикла, удовлетворяющих большую часть прикладных потребностей. Но в тех особых случаях, когда требуется преждевре­ менное прерывание шага итерации цикла, оператор continue предоставляет струк­ турированный способ осуществления такого прерывания.

Оператор return

Оператор return организует возврат из метода. Его можно также использовать для возврата значения. Более подробно он рассматривается в главе 6.

Оператор goto

Имеющийся в C# оператор goto представляет собой оператор безусловного пере­ хода. Когда в программе встречается оператор goto, ее выполнение переходит непо­ средственно к тому месту, на которое указывает этот оператор. Он уже давно "вышел из употребления" в программировании, поскольку способствует созданию "макарон­ ного" кода. Тем не менее оператор goto все еще находит применение — иногда даже эффективное. В этой книге не делается никаких далеко идущих выводов относительно правомочности использования оператора goto для управления программой. Следует, однако, подчеркнуть, что этому оператору трудно найти полезное применение, и поэ­ тому он не особенно нужен для полноты языка программирования. Хотя в некоторых случаях он оказывается удобным и дает определенные преимущества, если использу­ ется благоразумно. В силу этих причин оператор goto упоминается только в данном разделе книги. Главный недостаток оператора goto с точки зрения программирования заключается в том, что он вносит в программу беспорядок и делает ее практически неудобочитаемой. Но иногда применение оператора goto может, скорее, прояснить, чем запутать ход выполнения программы.

Для выполнения оператора goto требуется метка — действительный в С# иден­ тификатор с двоеточием. Метка должна находиться в том же методе, где и оператор goto, а также в пределах той же самой области действия. В приведенном ниже при­ мере программы цикл суммирования чисел от 1 до 100 организован с помощью опе­ ратора goto и соответствующей метки.

х = 1;
loop1:
    х++;
    if(x < 100) goto loop1;

Кроме того, оператор goto может быть использован для безусловного перехода к ветви case или default в операторе switch. Формально ветви case или default выполняют в операторе switch роль меток. Поэтому они могут служить адресатами оператора goto. Тем не менее оператор goto должен выполняться в пределах опе­ ратора switch. Это означает, что его нельзя использовать как внешнее средство для безусловного перехода в оператор switch. В приведенном ниже примере программы демонстрируется применение оператора goto в операторе switch.

// Применить оператор goto в операторе switch.
using System;

class SwitchGoto {
    static void Main() {
        for(int i=1; i < 5; i++) {
            switch(i) {
                case 1:
                    Console.WriteLine("В ветви case 1");
                    goto case 3;
                case 2 :
                    Console.WriteLine("В ветви case 2");
                    goto case 1;
                case 3:
                    Console.WriteLine("В ветви case 3");
                    goto default;
                default:
                    Console.WriteLine("В ветви default");
                    break;
            }
            Console.WriteLine();
        }
        // goto case 1; // Ошибка! Безусловный переход к оператору switch недопустим.
    }
}

Вот к какому результату приводит выполнение этой программы.

В ветви case 1
В ветви case 3
В ветви default
В ветви case 2
В ветви case 1
В ветви case 3
В ветви default
В ветви case 3
В ветви default
В ветви default

Обратите внимание на то, как оператор goto используется в операторе switch для перехода к другим его ветвям case иди к ветви default. Обратите также внимание на то, что ветви case не оканчиваются оператором break. Благодаря тому что опе­ ратор goto препятствует последовательному переходу от одной ветви case к другой, упоминавшееся ранее правило недопущения "провалов" не нарушается, а следова­ тельно, необходимость в применении оператора break в данном случае отпадает. Но как пояснялось выше, оператор goto нельзя использовать как внешнее средство для безусловного перехода к оператору switch. Так, если удалить символы комментария в начале следующей строки:

// goto case 1; // Ошибка! Безусловный переход к оператору switch недопустим.

приведенная выше программа не будет скомпилирована. Откровенно говоря, при­ менение оператора goto в операторе switch, в общем, не рекомендуется как стиль программирования, хотя в ряде особых случаев это может принести определенную пользу.

Ниже приведен один из полезных примеров применения оператора goto для выхода из глубоко вложенной части программы.

// Продемонстрировать практическое применение оператора goto.
using System;

class Use_goto {
    static void Main() {
        int i=0, j=0, k=0;
        for(i=0; i < 10; i++) {
            for(j=0; j < 10; j++ ) {
                for(k=0; k < 10; k++) {
                    Console.WriteLine("i, j, k: " + i + " " + j +
                                    " " + k);
                    if(k == 3) goto stop;
                }
            }
        }
        stop:
        Console.WriteLine("Остановлено! i, j, k: " + i +
                        ", " + j + " " + k);
    }
}

Выполнение этой программы дает следующий результат.

i, j, k: 0 0 0
i, j, k: 0 0 1
i, j, k: 0 0 2
i, j, k: 0 0 3
Остановлено! i, j, k: 0, 0 3

Если бы не оператор goto, то в приведенной выше программе пришлось бы при­ бегнуть к трем операторам if и break, чтобы выйти из глубоко вложенной части этой программы. В данном случае оператор goto действительно упрощает код. И хотя при­ веденный выше пример служит лишь для демонстрации применения оператора goto, вполне возможны ситуации, в которых этот оператор может на самом деле оказаться полезным.

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