Не обнаружил проекты, которые используются в примерах в лекции, также не увидел список задач. |
Выражения. Операции в выражениях
Выражения
Выражения строятся из операндов - констант, переменных, функций, - объединенных знаками операций и скобками. При вычислении выражения определяется его значение и тип. Эти характеристики однозначно задаются значениями и типами операндов, входящих в выражение, и правилами вычисления выражения. Правила также задают:
- приоритет операций ;
- для операций одного приоритета порядок применения - слева направо или справа налево;
- преобразование типов операндов и выбор реализации для перегруженных операций;
- тип и значение результата выполнения операции над заданными значениями операндов определенного типа.
Программист, записывающий выражение, должен знать, по каким правилам оно будет вычисляться. Сложность в том, что эти правила, начиная с приоритета операций, варьируются от языка к языку. Давайте посмотрим, как это делается в C#.
Приоритет и порядок выполнения операций
Большинство операций в языке C#, их приоритет и порядок наследованы из языка C++. Однако имеются и различия: например, нет операции " , ", позволяющей вычислять список выражений ; добавлены уже упоминавшиеся операции checked и unchecked, применимые к выражениям.
Как это обычно делается, приведем таблицу приоритетов операций, в каждой строке которой собраны операции одного приоритета, а строки следуют в порядке приоритетов, от высшего к низшему.
Перегрузка операций
Под перегрузкой операции понимается существование нескольких реализаций одной и той же операции. Большинство операций языка C# перегружены - одна и та же операция может применяться к операндам различных типов. Поэтому перед выполнением операции идет поиск реализации, подходящей для данных типов операндов. Замечу, что операции, как правило, выполняются над операндами одного типа. Если же операнды разных типов, то предварительно происходит неявное преобразование типа операнда. Оба операнда могут быть одного типа, но преобразование типов может все равно происходить - по той причине, что для заданных типов нет соответствующей перегруженной операции. Такая ситуация достаточно часто возникает на практике, поскольку, например, операция сложения не определена для младших подтипов арифметического типа. Приведу начальный фрагмент процедуры Express, предназначенной для анализа выражений:
/// <summary> /// Анализ выражений /// </summary> public void Express() { //перегрузка операций byte b1 =1, b2 =2, b3; short sh1; int in1; //b3 = b1 + b2; //ошибка: результат типа int b3 = (byte)(b1+b2); //sh1 = b1 + b2; //ошибка: результат типа int sh1 = (short)(b1+b2); in1 = b1+ b2 + sh1; Console.WriteLine("b3= " + b3 + " sh1= "+ sh1 +" in1= " + in1); }//Express
Разберем этот фрагмент. Начнем с первого закомментированного оператора присваивания b3 = b1+b2;. Выражение здесь простейшее, включает одну бинарную операцию сложения. Оба операнда имеют тип byte, казалось бы, и результат должен быть типа byte и без помех присвоен переменной b3. Однако это не так. Для данных типа byte нет перегруженной реализации сложения. Ближайшей операцией является сложение целых типа int. Поэтому оба операнда преобразуются к типу int, выполняется операция сложения, результат имеет тип int и не может быть неявно преобразован в тип byte, - возникает ошибка еще на этапе компиляции. Корректная запись показана в следующем операторе. Аналогичная ситуация возникает, когда в левой части оператора стоит переменная типа short, - и здесь необходимо явное приведение к типу. Этого приведения не требуется, когда в левой части стоит переменная типа int.
Давайте разберем, как в данном примере организован вывод в методе WriteLine. До сих пор при вызове метода задавалось несколько параметров и использовалась форма вывода данных с подстановкой значений параметров в строку, заданную первым параметром. Здесь же есть только один параметр - это строка, заданная сложным выражением. Операция, многократно применяемая в этом выражении, это сложение " + ". Операнды сложения имеют разный тип: левый операнд имеет тип string, правый - арифметический ( byte, short, int ). В этом случае арифметический тип преобразуется к типу string и выполняется сложение строк (конкатенация). Напомню, при преобразовании арифметического типа к типу string вызывается метод ToString(), определенный для всех встроенных типов. Результатом этого выражения является строка, она и будет результатом вывода метода WriteLine.
Полагаю, что разбор данного примера и материалы предыдущей лекции, где приводилась иерархия преобразований внутри арифметического типа и обсуждались вопросы выбора реализации перегруженного метода, дают необходимое представление о том, как работает перегрузка операций при вычислении выражений. В деталях, как всегда, может помочь справочная система.
С чего начинается выполнение выражения
Вычисление выражения начинается с выполнения операций высшего приоритета. Первым делом вычисляются выражения в круглых скобках - (expr), определяются значения полей объекта - x.y, вычисляются функции - f(x), переменные с индексами - a[i]. Выполнение этих операций достаточно понятно и не нуждается в комментировании. Операции checked и unchecked включают и выключают проверку преобразований арифметического типа в выражениях, которым они предшествуют. О других операциях этой категории скажу чуть подробнее.
Операции "увеличить" и "уменьшить" (increment, decrement)
Операции "увеличить на единицу" и " уменьшить на единицу " могут быть префиксными и постфиксными. К высшему приоритету относятся постфиксные операции x++ и x--. Префиксные операции имеют на единицу меньший приоритет. Главной особенностью как префиксных, так и постфиксных операций является побочный эффект, в результате которого значение x увеличивается ( ++ ) или уменьшается ( -- ) на единицу. Для префиксных ( ++x, --x ) операций результатом их выполнения является измененное значение x, постфиксные операции возвращают в качестве результата значение x до изменения. Приведу пример применения этих операций, дополнив метод Express новым фрагментом:
//операции increment и decrement //Следующее выражение допустимо, но писать подобное никогда не следует in1 = ++in1 +in1+ in1++; //in2 = ++in1 + in1++ + in1; Console.WriteLine(" in1= " + in1);
Обратите внимание, что хотя у постфиксной операции высший приоритет, это вовсе не означает, что при вычислении выражения вначале выполнится операция in1++, затем ++in1, и только потом будет проводиться сложение. Нет, вычисления проводятся в том порядке, в котором они написаны. Поскольку на входе значение in1 было равно 6, то выражение будет вычисляться следующим образом:
7(7) + 7 + 7(8),
где в скобках записан побочный эффект операции. Так что консольный вывод даст следующий результат:
in1 = 21
Операциями " увеличить " и " уменьшить " не следует злоупотреблять. Уже оператор, приведенный в нашем фрагменте, сложен для понимания из-за побочного эффекта. Понимаете ли вы, что при изменении порядка записи слагаемых, как это сделано в закомментированном операторе, результат вычисления выражения будет уже не 21, а 22?