Логические операции
Начну с предупреждения тем, кто привык к языку C++. Правила работы с логическими выражениями в языках C# и C++ имеют принципиальные различия. В языке C++ практически для всех типов существует неявное преобразование в логический тип. Правило преобразования простое, - ненулевые значения трактуются как истина, нулевое - как ложь. В языке C# неявных преобразований к логическому типу нет даже для целых арифметических типов. Поэтому вполне корректная в языке C++ запись:
int k1 = 7; if (k1) Console.WriteLine("ok!");
незаконна в программах на C#. На этапе трансляции возникнет ошибка, поскольку вычисляемое условие имеет тип int, а неявное преобразование этого типа к типу bool отсутствует.
В языке C# более строгие правила действуют и для логических операций. Так, запись
if(k1 && (x>y)),
корректная в языке C++, приводит к ошибке в программах на C#, поскольку операция && определена только для операндов типа bool, а в данном выражении один из операндов имеет тип int. В языке C# в данных ситуациях следует использовать записи:
if(k1>0) if((k1>0) && (x>y))
После этого важного предупреждения перейду к более систематическому изложению некоторых особенностей выполнения логических операций. Так же, как и в языке C++, логические операции делятся на две категории: одни выполняются над логическими значениями операндов, другие осуществляют выполнение логической операции над битами операндов. По этой причине в C# существуют две унарные операции отрицания - логическое отрицание, заданное операцией "!", и побитовое отрицание, заданное операцией "~". Первая из них определена над операндом типа bool, вторая - над операндом целочисленного типа, начиная с типа int и выше (int, uint, long, ulong). Результатом операции во втором случае является операнд, в котором каждый бит заменен его дополнением. Приведу пример:
/// <summary> /// Логические выражения /// </summary> public void Logic() { //операции отрицания ~,! bool b1,b2; b1 = 2*2==4; b2 =!b1; //b2= ~b1; uint j1 =7, j2; j2= ~j1; //j2 = !j1; int j4 = 7, j5; j5 = ~j4; Console.WriteLine("uint j2 = " + j2 +" int j5 = " + j5); }//Logic
В этом фрагменте закомментированы операторы, приводящие к ошибкам. В первом случае была сделана попытка применения операции побитового отрицания к выражению типа bool, во втором - логическое отрицание применялось к целочисленным данным. И то, и другое в C# незаконно. Обратите внимание на разную интерпретацию побитового отрицания для беззнаковых и знаковых целочисленных типов. Для переменных j5 и j2 строка битов, задающая значение - одна и та же, но интерпретируется по-разному. Соответствующий вывод таков:
uint j2 = 4294967288 int j5 = -8
Бинарные логические операции "&& - условное И" и " - условное ИЛИ" определены только над данными типа bool. Операции называются условными или краткими, поскольку, будет ли вычисляться второй операнд, зависит от уже вычисленного значения первого операнда. В операции "&&", если первый операнд равен значению false, то второй операнд не вычисляется и результат операции равен false. Аналогично, в операции "", если первый операнд равен значению true, то второй операнд не вычисляется и результат операции равен true. Ценность условных логических операций заключается не в их эффективности по времени выполнения. Часто они позволяют вычислить логическое выражение, имеющее смысл, но в котором второй операнд не определен. Приведу в качестве примера классическую задачу поиска по образцу в массиве, когда разыскивается элемент с заданным значением (образец). Такой элемент в массиве может быть, а может и не быть. Вот типичное решение этой задачи в виде упрощенном, но передающем суть дела:
//Условное And - && int[] ar= {1,2,3}; int search = 7; int i=0; while ((i < ar.Length) && (ar[i]!= search)) i++; if(i<ar.Length) Console.WriteLine("Образец найден"); else Console.WriteLine("Образец не найден");
Если значение переменной search (образца) не совпадает ни с одним из значений элементов массива ar, то последняя проверка условия цикла while будет выполняться при значении i, равном ar.Length. В этом случае первый операнд получит значение false, и, хотя второй операнд при этом не определен, цикл нормально завершит свою работу. Второй операнд не определен в последней проверке, поскольку индекс элемента массива выходит за допустимые пределы (в C# индексация элементов начинается с нуля). Заметьте, что "нормальная" конъюнкция требует вычисления обеих операндов, поэтому ее применение в данной программе приводило бы к выбросу исключения в случае, когда образца нет в массиве.
Три бинарные побитовые операции - "& - AND " , "| - OR ", "^ - XOR" используются двояко. Они определены как над целыми типами выше int, так и над булевыми типами. В первом случае они используются как побитовые операции, во втором - как обычные логические операции. Иногда необходимо, чтобы оба операнда вычислялись в любом случае, тогда без этих операций не обойтись. Вот пример первого их использования:
//Логические побитовые операции And, Or, XOR (&,|,^) int k2 = 7, k3 = 5, k4, k5, k6; k4 = k2 & k3; k5 = k2| k3; k6 = k2^k3; Console.WriteLine("k4 = " + k4 + " k5 = " + k5 + " k6 = " + k6);
Приведу результаты вывода:
k4 = 5 k5 = 7 k6 =2
Приведу пример поиска по образцу с использованием логического AND:
i=0; search = ar[ar.Length - 1]; while ((i < ar.Length) & (ar[i]!= search)) i++; if(i<ar.Length) Console.WriteLine("Образец найден"); else Console.WriteLine("Образец не найден");
В данном фрагменте гарантируется наличие образца поиска в массиве, и фрагмент будет успешно выполнен. В тех же случаях, когда массив не содержит элемента search, будет выброшено исключение. Содержательный смысл такой процедуры - появление исключения - может быть признаком ошибки в данных, что требует специальной обработки ситуации.