Компонентный подход в программировании

Инструкции


Большинство видов инструкций в Java и C# являются общими и заимствованы из языка C. В обоих языках есть понятие блока — набора инструкций, заключенного в фигурные скобки.

  • Пустая инструкция ; допускается в обоих языках.
  • Декларации локальных переменных устроены совершенно одинаково — указывается тип переменной, затем ее идентификатор, а затем, возможно, инициализация.

    Инициализировать переменную можно каким-то значением ее типа. Использование неинициализированных переменных во многих случаях определяется компилятором и считается ошибкой (но не всегда). Однако даже при отсутствии инициализации переменной, ей все равно будет присвоено значение по умолчанию для данного типа.

    Массивы могут быть инициализированы с помощью специальных выражений, перечисляющих значения элементов массива, например

    int[][] array = new int[][]{{0, 1}, {2, 3, 4}}; Ag

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

    • присваиванием;
    • выражением, в котором последним оператором было уменьшение или увеличение на единицу (++, --), все равно, префиксное или постфиксное;
    • вызовом метода в объекте или классе (в C# — еще и вызовом делегата);
    • созданием нового объекта.
  • Условная инструкция имеет вид

    if(expression) statement Ag

    или

    if(expression) statement else statement1 Ag

    где expression — выражение логического типа (или приводящегося к логическому), а statement и statement1 — инструкции.

  • Инструкция выбора имеет вид

    switch(expression) { … } Ag

    Внутри ее блока различные варианты действий для различных значений выражения expression описываются с помощью списков инструкций, помеченных либо меткой case с возможным значением выражения, либо меткой default. Группа инструкций, помеченная default, выполняется, если значение выражения выбора не совпало ни с одним из значений, указанных в метках case.
    Один набор инструкций может быть помечен несколькими метками. Наборы инструкций могут отделяться друг от друга инструкциями break;

    Тип expression может быть целочисленным или приводящимся к нему, либо перечислимым типом. В C# допускается использование для выбора выражений типа string.

    Значения, которые используются в метках case, должны быть константными выражениями.





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


    В C# группа инструкций для одного значения (включая и группу, помеченную default) всегда должна оканчиваться либо break, либо goto default, либо goto case value с каким-то из значений, указанных в рамках той же инструкции выбора.


    public class A { public static void main(String[] args) { if(args.length > 0) { int n = Integer.parseInt(args[0]); switch(n) { case 0: System.out.println("n = 0");

    case 1: System.out.println ("n = 0 or n = 1"); break; case 2:case 3: System.out.println ("n = 2 or n = 3"); break; default: System.out.println ("n is out of [0..3]");

    } } else System.out.println("No arguments"); } }
    using System;

    public class A { public static void Main(string[] args) { if(args.Length > 0) { int n = Int32.Parse(args[0]); switch(n) { case 0: Console.WriteLine("n = 0"); goto case 1; case 1: Console.WriteLine ("n = 0 or n = 1"); break; case 2:case 3: Console.WriteLine ("n = 2 or n = 3"); break; default: Console.WriteLine ("n is out of [0..3]"); break; } } else Console.WriteLine("No arguments"); } }
  • Циклы while и do в обоих языках устроены одинаково:

    while(expression) statement Ag do statement while(expression); Ag

    Здесь expression — логическое выражение, условие цикла, statement — тело цикла. Правила выполнения этих циклов фактически заимствованы из языка C. Первый на каждой итерации проверяет условие и, если оно выполнено, выполняет свое тело, а если нет — передает управление дальше.


    Второй цикл сначала выполняет свое тело, а потом проверяет условие.

  • Цикл for в обоих языках заимствован из языка C.

    for(A; B; C) statement Ag

    выполняется практически как

    A; while(B) { statement C; } Ag

    Любой из элементов A, B, C может отсутствовать, B должно быть выражением логического типа (при отсутствии оно заменяется на true), A и С должны быть наборами выражений (A может включать и декларации переменных), разделенных запятыми.

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



    В Java синтаксис цикла перебора элементов коллекции такой:

    for ( finalopt type id : expression ) statement

    При этом выражение expression должно иметь тип java.lang.Iterable или тип массива.

    В первом случае такой цикл эквивалентен следующему (T далее обозначает тип результат метода iterator() у expression, v — нигде не используемое имя).

    for(T v = expression.iterator(); v.hasNext(); ) { finalopt type id = v.next(); statement }

    Во втором случае, когда expression — массив типа T[], эта конструкция эквивалентна следующей (a, i — нигде не используемые имена)

    T[] a = expression; for(int i = 0; i < a.length; i++) { finalopt type id = v.next(); statement }



    В C# синтаксис цикла перебора элементов коллекции такой:

    foreach ( type id in expression ) statement

    Выражение expression должно быть массивом, или иметь тип System.Collections.IEnumerable или System.Collections.Generic.IEnumerable <T>, или же его тип должен иметь метод GetEnumerator(), результат которого, в свою очередь, должен иметь свойство Current и метод MoveNext().

    Тип результата метода GetEnumerator() во всех случаях, кроме массива, называется типом итератора (enumerator type). Тип свойства Current, которое имеется у типа итератора, должен совпадать с type.

    Пусть тип итератора E, а e — неиспользуемое имя. Тогда приведенная конструкция, с точностью до некоторых деталей, эквивалентна следующей.

    E e = expression.GetEnumerator(); while(e.MoveNext()) { type id = (type)e.Current; statement }

    Опущенные детали касаются освобождения ресурсов, используемых итератором (см. далее описание инструкции using).


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


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


    public class A { public static void main(String[] args) { int i = 1; for(String s : args) System.out.println((i++) + "-th argument is " + s); } }



    using System;

    public class A { public static void Main(string[] args) { int i = 1; foreach (string s in args) Console.WriteLine((i++) + "-th argument is " + s); } }
    <


  • Инструкции прерывания break и continue также заимствованы из C.

    Инструкция break прерывает выполнение самого маленького содержащего ее цикла и передает управление первой инструкции после него. Инструкция continue прерывает выполнение текущей итерации и переходит к следующей, если она имеется (т.е. условие цикла выполнено в сложившейся ситуации), иначе тоже выводит цикл.

    При выходе с помощью break или continue за пределы блока try (см. ниже) или блока catch, у которых имеется соответствующий блок finally, сначала выполняется содержимое этого блока finally.



    В Java инструкция break используется для прерывания выполнения не только циклов, но и обычных блоков (наборов инструкций, заключенных в фигурные скобки).

    Более того, после break (или continue) может стоять метка. Тогда прерывается выполнение того блока/цикла (или же начинается новая итерация того цикла), который помечен этой меткой. Этот блок (или цикл) должен содержать такую инструкцию внутри себя.
                                                   
  • Инструкция возврата управления return используется для возврата управления из операции (метода, оператора, метода доступа к свойству и пр., см. далее). Если операция должна вернуть значение некоторого типа, после return должно стоять выражение этого же типа.
  • Инструкция создания исключительной ситуации throw используется для выброса исключительной ситуации. При этом после throw должно идти выражение, имеющее тип исключения.

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

  • Исключения в обоих языках относятся к особым типам — классам исключений.


    Только объекты таких классов могут быть выброшены в качестве исключений. Классами исключений являются все наследники классов java.lang.Throwable в Java и System.Exception в C#.
  • Объекты-исключения содержат, как минимум, следующую информацию.

    • Сообщение о возникшей ситуации (его должен определить автор кода операции, выбрасывающей это исключение).

      В Java это сообщение можно получить с помощью метода String getMessage(), а в C# — с помощью свойства string Message.

    • Иногда возникают цепочки "наведенных" исключений, если обработка одного вызывает выброс другого. Каждый объект-исключение содержит ссылку на другое исключение, непосредственно вызвавшее это. Если данное исключение не вызвано никаким другим, эта ссылка равна null.

      В Java эту ссылку можно получить с помощью метода Throwable getCause(), а в C# — с помощью свойства System.Exception.InnerException.

    • Для описания ситуации, в которой возникло исключение, используется состояние стека исполнения программы — список методов, которые вызывали друг друга перед этим, и указание на место в коде каждого такого метода. Это место обозначает место вызова следующего метода по стеку или, если это самый последний метод, то место, где и возникло исключение. Обычно указывается номер строки, но иногда он недоступен, если соответствующий метод присутствует в системе только в скомпилированном виде или является внешним для Java-машины.

      Информация о состоянии стека на момент возникновения исключения, как и его сообщение, автоматически выводится в поток сообщений об ошибках, если это исключение остается необработанным в программе.

      В Java состояние стека для данного исключения можно получить с помощью метода StackTraceElement[] getStackTrace(), возвращающего массив элементов стека. Каждый такой элемент несет информацию о файле (String getFileName()), классе (String getClassName()) и методе (String getMethodName()), а также о номере строки (int getLineNumber()).

      В C# можно сразу получить полное описание состояния стека в виде одной строки с помощью свойства string StackTrace.



  • Блок обработки исключительных ситуаций выглядит так:

    try { statements } Ag catch ( type_1 e_1 ) { statements_1 } Ag … Ag catch ( type_n e_n ) { statements_n } Ag finally { statements_f } Ag

    Если во время выполнения одной из инструкций в блоке, следующем за try, возникает исключение, управление передается на первый блок catch, обрабатывающий исключения такого же или более широкого типа. Если подходящих блоков catch нет, выполняется блок finally и исключение выбрасывается дальше.

    Блок finally выполняется всегда — сразу после блока try, если исключения не возникло, или сразу после обрабатывавшего исключение блока catch, даже если он выбросил новое исключение.

    В этой конструкции могут отсутствовать блоки catch или блок finally, но не то и другое одновременно. В C# разрешается опускать имя объекта-исключения в catch, если он не используется при обработке соответствующей исключительной ситуации.

public class A { public static void main(String[] args) { try { if(args.length > 0) System.out.println ("Some arguments are specified"); else throw new IllegalArgumentException ("No arguments specified"); } catch(RuntimeException e) { System.out.println ("Exception caught"); System.out.println ("Exception type is " + e.getClass().getName()); System.out.println ("Exception message is \"" + e.getMessage() + "\""); } finally { System.out.println ("Performing finalization"); } } }using System;

public class A { public static void Main(string[] args) { try { if(args.Length > 0) Console.WriteLine ("Some arguments are specified"); else throw new ArgumentException ("No arguments specified"); } catch(Exception e) { Console.WriteLine ("Exception caught"); Console.WriteLine ("Exception type is " + e.GetType().FullName); Console.WriteLine ("Exception message is \"" + e.Message + "\""); } finally { Console.WriteLine ("Performing finalization"); } } }


В Java, начиная с версии 1.4, появилась инструкция assert, предназначенная для выдачи отладочных сообщений.

Эта инструкция имеет один из двух видов:

assert expression ; assert expression : expression_s ;

Выражение expression должно иметь логический тип, а выражение expression_s — произвольный.

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

Если проверка утверждений включена, то вычисляется значение expression. Если оно равно true, управление переходит дальше, иначе в обоих случаях выбрасывается исключение java.lang.AssertionError.

Во втором случае еще до выброса исключения вычисляется значение выражения expression_s, оно преобразуется в строку и записывается в качестве сообщения в создаваемое исключение.


В C# имеется возможность использовать инструкцию goto. Эта инструкция передает управления на инструкцию, помеченную меткой, которая следует за goto.

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

При выходе с помощью goto из блока try или блока catch, у которых имеется соответствующий блок finally, сначала выполняется содержимое этого блока finally.


Ключевые слова checked и unchecked в C# могут помечать блок, определяя тем самым контекст вычислений в рамках этого блока (см. раздел о целочисленных типах).


Инструкция using может быть использована в C#, чтобы выполнить действия, требующие захвата каких-либо ресурсов, без необходимости заботиться потом об их освобождении.

Эта инструкция имеет вид:

using ( expression ) statement или using ( declaration ) statement

где declaration — это декларация одной или нескольких переменных.

Первый вид этой инструкции сводится ко второму — если тип используемого выражения T, и имя v нигде не используется, то он эквивалентен

using ( T v = expression ) statement Эта конструкция, в свою очередь, эквивалентна следующей. { T v = expression; try { statement } finally { disposal } }

Здесь disposal представляет вызов метода Dispose(), который должен быть у типа T, с возможной предварительной проверкой того, что переменная v не равна null, и приведением ее к типу System.IDisposable, если T является его подтипом.


В версии 2.0 в C# введены две инструкции yield, предназначенные для более удобного построения итераторов.

Блок, содержащий инструкцию yield, называется итерационным (iterator block) и может быть телом метода, оператора или метода доступа к свойству и не должен быть блоком finally, catch или блоком try, у которого есть соответствующие catch-блоки. Этот блок порождает последовательность значений одного типа. Сам метод или оператор должен возвращать объект одного из четырех типов:

System.Collections.IEnumerable, System.Collections.IEnumerator, System.Collections.Generic.IEnumerable <T>, System.Collections.Generic.IEnumerator <T>.

В первых двух случаях порождаются объекты типа object, во вторых двух — значения типа T.

Для возвращения одного из этой последовательности значений используется инструкция yield return expression; Выражение в ней должно иметь соответствующий тип, object или T.

Для указания на то, что порождаемая итерационным блоком последовательность значений завершилась, используется инструкция yield break;

Пример реализации итератора коллекции с использованием yield приведен ниже.
using System;

public class MyArrayList<T> { T[] items = new T[10]; int size = 0;

public int Count { get { return size; } }

public T this[int i] { get { if(i < 0 || i >= size) throw new IndexOutOfRangeException(); else return items[i]; } set { if(i < 0 || i > size) throw new IndexOutOfRangeException(); else if (i == size) { T[] newItems = new T[size + 10]; Array.Copy (items, newItems, size++); } items[i] = value; } }

public IEnumerator<T> GetEnumerator () { for(int i = 0; i < size; i++) yield return items[i]; } }

public class A { public static void Main() { MyArrayList<string> l = new MyArrayList<string>();

l[0] = "First"; l[1] = "Second"; l[2] = "Third";

foreach (string s in l) Console.WriteLine(s); } }
Инструкции обоих языков, предназначенные для синхронизации работы нескольких потоков, рассматриваются в следующей лекции, в разделе, посвященном разработке многопоточных программ.


Содержание раздела