Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
ЧТО ВАЖНЕЕ: ОПЕРАЦИИ ОТНОШЕНИЯ ИЛИ ВЫРАЖЕНИЯ Далее Содержание Операции отношения используются для сравнений. Мы уже использовали ранее некоторые из них, а сейчас приведем полный список операций отношения, применяемых при программировании на языке Си. Операция: Смысл: < меньше <= меньше или равно = = равно >= больше или равно > больше != не равно Этот список довольно хорошо соответствует возможным числовым соотношениям. (Вообще говоря, числа, даже комплексные, менее сложны, чем люди 4) ). Главное предостережение, которое мы хотим сделать, состоит в том, чтобы не использовать знак = вместо = =. В некоторых языках программирования (например. Бейсике) один и тот же знак используется и для операции присваива ния, и для операции отношения "равенство", хотя они совершенно различны. С помощью операции присваивания некоторое значение присваивается переменной слева от знака равенства. В то же время с помощью операции отношения "равенство" проверяется равно ли выражение, стоящее слева от знака, выражению справа от него. Эта операция не изменяет значения переменной в левой части, если она там присутствует. canoes = 3 присваивает значение 3 переменной canoes canoes = = 5 проверяет, равняется ли значение переменной canoes 5 При программировании требуется аккуратность, потому что в ряде случаев компилятор не сможет обнаружить ошибки, связанной с неправильным использованием знаков этих операций, что приведет к результатам, отличным от тех, которые вы должны были получить. Ниже мы кратко остановимся на одном примере. 126 Таблица 7.1. Операции присваивания и отношения "равенство" в некоторых языках программирования Язык Операция присваивания Операция отношения "равенство" Бейсик = = Фортран = EQ Си = == Паскаль = = ПЛ/1 = = Лого make - Операции отношения применяются при формировании условных выражений, используемых в операторах if и while. Указанные oпeраторы проверяют, истинно или ложно данное выражение. Ниже приводятся четыре не связанные между собой оператора, содержащие условные выражения; их смысл, мы надеемся, понятен. if(number < 6) printf(" Ваше число слишком мало.\n" ); whi1е(сh!= '$') сount ++; if(total == 100) printf(" Вы набрали максимум очков!\n"); if (ch > 'м') printf(" Отправьте этого человека по другому маршруту \n"); РИС. 7.4. Операции = и = =. 127 Обратите внимание, что в условных выражениях разрешается использовать также и символы, причем при сравнении их берется машинный код (который, как предполагалось вначале, является ко дом ASCII). Однако использовать операции отношения для сравнения строк не разрешается в гл 13. мы представим средства работы со строками. Операции отношения можно использовать также при работе с числами с плавающей точкой. Однако при сравнениях этих чисел мы рекомендуем вам ограничиться лишь операциями < и >, так как ошибки округления могут привести к тому, что числа окажутся неравными, хотя по логике они должны быть равны. Рассмотрим подобную ситуацию на примере десятичных чисел. Очевидно, произведение 3 и 1/3 равно 1.0, но если мы представим 1/3 в виде 6 разрядной десятичной дроби, то произведение будет равно 0,999999, что не равняется в точности 1. Что такое истина? Далее Содержание Каждое условное выражение проверяется "истинно" ли оно или ложно. При этом возникает интересный вопрос: Что такое истина? Мы можем ответить на этот вечный вопрос покрайней мере так, как он решен в языке Си. Напомним, во-первых, что выражение в Си всегда имеет значение. Это утверждение остается вepным даже для условных выражений, как показывает пример, приведенный ниже. В нем определяются значения двух условных выражений, одно из которых оказывается истинным, а второе - ложным. /* истина и ложь */ main( ) { int true, false; true = (10 > 2); /* отношение истинно */ false = (10 = =2); /* отношение ложно */ printf("true = %d; false = %d\n" , true, false); } В данном примере значения двух условных выражений присваиваются двум переменным. Чтобы не запутать читателя, мы присвоили переменной true значение выражения, которое оказывается истинным, а переменной false - значение выражения, которое оказывается ложным. При выполнении программы получим следующий простой результат: true = 1; false = 0 Вот как! Оказывается, в языке Си значение "истина" - это 1, a "ложь" - 0. Мы можем это легко проверить, выполнив программу, приведенную ниже. /* проверка истинности */ main( ) { if(1) printf(" 1 соответствует истине.\n" ); else printf(" 1 не соответствует истине. \n"); if(0) printf(" 0 не означает ложь. \n"); else printf(" 0 означает ложь. \n"); } Мы скажем, что 1 должна рассматриваться как истинное утверждение, а 0 - как ложное. Если наше мнение верно, то в первом операторе if должна выполниться первая ветвь (ветвь if, а во втором операторе if - вторая (ветвь else). Попробуйте запустить программу, чтобы узнать, правы ли мы. Итак чему же все-таки соответствует истина? Далее Содержание Мы можем использовать 1 и 0 в качестве проверочных значений оператора if. Спрашивается, 128 можем ли мы использовать другие числа. Если да, то что при этом происходит? Давайте проведем эксперимент. /* if - тест */ main( ) { if (200) printf("200 - это истина. \n"); if(-33) printf(" -33 - это истина \n"); } Pезультаты выглядят так 200 - это истина -33- это истина Очевидно, в языке Си числа 200 и -33 тоже рассматриваются как "истина". И действительно, все ненулевые величины принимаются в качестве "истины" и только 0 - как "ложь". В языке Си понятие истины оказывается чрезвычайно растяжимым. Многие программисты обычно пользуются этим определением истинности. Например, строку if(goats !=0) можно заменить такой if(goats) поскольку выражение (goats != 0) и выражение (goats) оба примут значение 0, или "ложь", только в том случае, если значение переменной goats равно 0. Мы думаем, что смысл второй формы записи менее очевиден, чем первой. Однако в результате компиляции она оказывается более эффективной, так как при реализации про граммы требует меньшего числа машинных операций. Осложнения с понятием "истина" Далее Содержание Растяжимость понятия истина в языке Си может привести и к неприятностям. Рассмотрим следующую программу: /* занятость */ main( ) { int age = 20; while(age++ <= 65) { if((age % 20) = =0) /* делится ли возраст на 20 ? */ printf(" Вам %d. Поздравляем с повышением!\n", age); if (age = 65) printf(" Вам %d. Преподносим Вам золотые часы \n", age); } } С первого взгляда вам может показаться, что результат работы программы будет выглядеть, например, так: Вам 40. Поздравляем с повышением Вам 60. Поздравляем с повышением Вам 65. Преподносим Вам золотые часы На самом деле, однако, выход будет таким: Вам 65. Преподносим Вам золотые часы Вам 65. Преподносим Вам золотые часы Вам 65. Преподносим Вам золотые часы 129 Вам 65. Преподносим Вам золотые часы Вам 65. Преподносим Вам золотые часы и т. д.- до бесконечности. В чем дело? Это произошло не только потому, что мы плохо спроектировали программу, но и потому, что мы забыли свои собственные предостережения и использовали выражение: if (agе = 65) вместо if (age == 65) Последствия ошибки оказались катастрофическими. Когда в процессе выполнения программа достигает указанного оператора, она проверит выражение (аgе = 65). Это выражение, включающее в себя опреацию присваивания, имеет значение, которое совпадает со значением переменной, расположенной слева от знака, т.е. с 65 (в любом случае). Поскольку 65 не равно нулю, выражение считается истинным" и выполняется команда вывода на печать. Затем, когда в программе происходит передача управления на команду проверки условия в цикле while, значение переменной аgе по-прежнему равно 65, что меньше или равно 65. Условие оказывается истинным и величина аgе увеличивается до 66 (ввиду наличия операции увеличения ++ в постфиксной форме), и тело цикла выполняется еще раз. Прекратится ли его выполнение на следующем шаге? Должно было бы, поскольку величина аgе теперь больше, чем 65. Когда программа опять попадает на наш ошибочный оператор if переменная аgе снова получит значение 65. В результате сообщение будет напечатано еще раз, затем тело цикла выполнится еще раз, и т.д. - до бесконечности (Конечно, если вы в конце концов не захотите остановить программу). Подводя итоги, можно сказать, что операции отношения используется для образования условных выражений. Условное выражение имеет значение "1", когда оно истинно, и "0", если оно ложно. В операторах (таких как while и if), где обычно используются условные выражения для задания проверяемых условий, могут применяться любые выражения, причем ненулевое значение является истиной", а нуль - "ложью". Приоритеты операций отношения Далее Содержание Приоритет операций отношения считается меньшим, чем у операций + и -, и больше, чем у операции присваивания. Например, значение выражения: х > y + 2 то же, что и выражения х > (у + 2) Это означает также, что выражение ch = getchar( ) != EOF эквивалентно ch = (getchar( ) != EOF) поскольку наличие у оператора !=, более высокого приоритета говорит о том, что она выполняется перед присваиванием. Поэтому значение переменной ch может стать либо 1, либо 0 ввиду того, что (getchar( ) != EOF) - условное выражение, значение которого присваивается переменной ch. Поскольку в примерах программ рассмотренных до сих пор, предполагалось, что переменная ch получает свое значение от функции getchar( ), мы использовали скобки, чтобы организовать выполнение операций в нужном порядке. 130 (ch = getchar( )) != EOF Сами операции отношения можно разбить на две группы в соответствии с назначенными приоритетами: группа операций более высокого приоритета: < <= => > группа операций более низкого приоритета: = = != Подобно большинству остальных операций операции отношения выполняются слева направо. Поэтому под записью: ех != wye == zee подразумевается (ex != wye) == zee Следовательно, в соответствии с правилами языка Си сначала проверяется, равны ли значения переменных ех и wye. Результирующая величина, равная 1 или 0 (истина или ложь), затем сравнивается со значением zee. Мы не видим реальной необходимости использовать подобного сорта конструкцию, но считаем своим долгом указать на возможные следствия принятого порядка выполнения операций. Читателю, озабоченному сохранением своего высокого приоритета, хотим напомнить, что полный список всех операций вместе с их приоритетами приведен в приложении В в конце книги. Резюме: операции отношения и выражения 1. Операции отношения С помощью каждой из приведенных ниже операции величина слева от знака сравнивается с величиной справа от него: 2. Больше 3. Больше или равно 4. Равно 5. Меньше или равно 6. Меньше 7. Не равно УСЛОВHЫЕ ВЫРАЖЕНИЯ Понятие условное выражение состоит из знака операции отношения и операндов, расположенных слева и справа от него. Если отношение истинно, значение условного выражения равно 1, если отношение ложно, значение условного выражения равно 0. Примеры: Отношение 5 > 2: истинно и имеет значение 1. Отношение (2 + а) = = а: ложно и имеет значение 0. ЛОГИЧЕСКИЕ ОПЕРАЦИИ Далее Содержание Иногда бывает полезным объединить два или более условных выражения. Например, предположим, нам требуется программа, которая подсчитывает только "непустые" символы, т. е. мы хотим знать число символов, не являющихся пробелами, символами "новая строка" и табуляции. Для этого мы можем использовать "логические" операции. Ниже приводится короткая 131 программа люстрирующая этот способ подсчета: /* число символов */ /* подсчитывает непустые символы */ main( ) { int ch; int charcount = 0; while ((ch = getchar( )) != EOF) if(ch !=' ' && ch \='\n' && ch != '\t') charcount++; printf(" Всего %d непустых символов. \n", charcount); } Так же как это обычно происходило в наших предыдущих программах, данная программа начинает свое выполнение с чтением символа и проверки, является ли он признаком конца файла. Дальше появляется нечто новое - оператор, использующий логическую операцию "и", обозначаемую &&. Смысл действий, осуществляемых оператором if в данном случае, можно пояснить следующим образом: Если прочитанный символ не пробел, И не символ "новая строка", И не символ табуляции, то происходит увеличение значения переменной charcount на единицу. Все выражение будет истинным, если указанные три условия истинны. Логические операции имеют меньший приоритет, чем операции отношения, поэтому не было необходимости использовать дополнительные скобки для выделения подвыражений. В языке Си имеются три логические операции: Операция Смысл && И || ИЛИ ! НЕ Предположим, что expl и ехр2 - два простых условных выражения типа cat > rat или debt = = 1000. Тогда: 1. expl && ехр2: истинно в том и только в том случае, когда оба выражения expl и ехр2 истинны. 2. expl || ехр2: истинно в том случае, если какое-нибудь из выражений expl или ехр2 истинно или оба истинны. 3. !expl: истинно, если выражение expl ложно, и наоборот. Ниже приведено несколько конкретных примеров: 5 > 2 && 4 > 7: ложно, поскольку истинно только одно подвыражение. 5 > 2 || 4 > 7: истинно, поскольку по крайней мере одно из подвыражений истинно. !(4 > 7): истинно, потому что 4 не больше 7. Последнее выражение к тому же эквивалентно следующему: 4 < = 7. Если вы совсем не знакомы с логическими операциями или испытываете трудности при работе с ними, помните, что практика && время = = совершенство. Приоритеты Далее Содержание 132 Операция ! имеет очень высокий приоритет, он выше, чем у умножения, такой же, как у операций увеличения, и только круглые скобки имеют более высокий приоритет. Приоритет операции && больше чем операции ||, а обе они имеют более низкий приоритет, чем операции отношения, но более высокий, чем операция присваивания. Поэтому выражение: а > b && b > c || b > d интерпретировано так: ((a > b) && (b > с)) || (b > d) т. е. b содержится между с и а или b больше d. Порядок вычислений Далее Содержание Обычно в языке Си не определяется, какие части сложного выражения будут вычисляться вначале. Например, в операторе: apples = (5 + 3)*(9 + 6); выражение 5 + 3 может быть вычислено до вычисления выражения 9 + 6, или наоборот (Приоритеты, присвоенные операциям гарантатируют, что оба выражения будут вычислены перед выполнением операции умножения.) Эта неопределенность была оставлена в языке, чтобы создатели компилятора имели возможность в конкретной системе осуществлять наиболее эффективный выбор. Исключением из этого правила (или его нарушением) является выполнение логических операций. Язык Си гарантирует, что логические выражения вычисляются слева направо. Более того, гарантируется также, что, как только обнаруживается элемент, значение которого устанавливает ложность всего выражения как целого, вычисление данного выражения прекращается. Это дает возможность использовать конструкции типа: while((c = getchar( )) != EOF && с != '\n') В результате вычисления первого подвыражения переменная с получает свое значение, которое затем можно использовать во втором подвыражении. Если бы такой порядок вычислений не гарантировался, при выполнении программы компьютер, возможно, проверял бы истинность второго выражения перед нахождением значения переменной с. Приведем еще один пример: if (number !=0 && 12/number ==2) printf(" Число равно 5 или 6.\n" ); Если значение переменной number равно 0, то все выражение ложно, и поэтому дальнейшее вычисление данного условного выражения прекращается. Это избавляет компьютер от последствий деления на нуль. Многие языки не обеспечивают выполнения подобного требования, и, выяснив, что number равно 0, компьютер переходит к проверке следующего условия. Резюме: логические операции и выражения 1. ЛОГИЧЕСКИЕ ОПЕРАЦИИ Операндами логических операций обычно являются условные выражения. У операции != имеется только один операнд. Остальные имеют по два - один слева от знака и другой справа от него. && И || ИЛИ ! НЕ 133 II. ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ выражение1 && выражение2: истинно тогда и только тогда, когда оба выражения истинны выражение1 || выражение2: истинно, если какое-нибудь одно или оба выражения истинны !выражение: истинно, если выражение ложно, и на оборот ПОРЯДОК ВЫЧИСЛЕНИИ Логические выражения вычисляются слева направо; вычисления прекращаются, как только устанавливается истинность или ложность всего выражения. ПРИМЕРЫ Выражение: 6 > 2 && 3 = = 3: истинно Выражение: !(6 > 2 && 3 = = 3): ложно Выражение: х != 0 && 20/х < 5: второе выражение вычисляется только при условии, что х не равен нулю. Применим наши знания для написания двух программ. Наш первый пример довольно удобен для этого. ПРОГPAMMA ПОДСЧЕТА СЛОВ Далее Содержание Теперь у нас есть возможности для написания программы подсчета числа слов в тексте. (Она может также подсчитывать символы строки.) Решающим моментом является разработка способа, с помощью которого программа будет распознавать слова. Мы будем придерживаться сравнительно простого подхода и определим слово как последовательность символов, которая не содержит "пуcтых символов". Поэтому |