Керниган и Ритчи - Язык программирования Си (793773), страница 17
Текст из файла (страница 17)
В качестве примера приведем цикл,обеспечивающий печать n элементов массива по 10 на каждой строке с одним пробелом между колонками;каждая строка цикла, включая последнюю, заканчивается символом новой строки:for (i = 0; i < n; i++)printf("%6d%c", a[i], (i%10 == 9 || i == n-1) ? 'n' : ' ');Символ новой строки посылается после каждого десятого и после n-го элемента. За всеми другимиэлементами следует пробел.
Эта программа выглядит довольно замысловато, зато она более компактна, чемэквивалентная программа с использованием if-else. Вот еще один хороший пример6:printf("Вы имеете %d элемент%s.\n", n, (n%10==1 && n%100 ! = 11) ?" " : ((n%100 < 10 || n%100 > 20) && n%10 >= 2 && n%10 <= 4) ?"а" : "ов");Упражнение 2.10. Напишите функцию lower, которая переводит большие буквы в малые, используяусловное выражение (а не конструкцию if-else).2.12. Приоритет и очередность вычисленийВ таблице 2.1 показаны приоритеты и очередность вычислений всех операторов, включая и те, которые мыеще не рассматривали. Операторы, перечисленные на одной строке, имеют одинаковый приоритет; строкиупорядочены по убыванию приоритетов; так, например, *, / и % имеют одинаковый приоритет, которыйвыше, чем приоритет бинарных + и -.
"Оператор" () относится к вызову функции. Операторы -> и . (точка)обеспечивают доступ к элементам структур; о них пойдет речь в главе 6, там же будет рассмотрен и операторsizeof (размер объекта). Операторы * (косвенное обращение по указателю) и & (получение адреса объекта)обсуждаются в главе 5. Оператор "запятая" будет рассмотрен в главе 3.ОператорыВыполняются()[]->.!~++--*/%+-слева направо<<>>слева направо6слева направо+-*&(тип)sizeofсправа налевослева направоЭтот пример, учитывающий русскую грамматику, отличается от авторского оригинала. — Примеч. ред.<<===!=>>=слева направослева направо&слева направо^слева направо|слева направо&&слева направо||слева направо?:справа налево=+=-=*=/=%=&=^=|=<<=,>>=справа налевослева направоПримечание.
Унарные операторы +, -, * и & имеют более высокий приоритет, чем те же бинарныеоператоры.Заметим, что приоритеты побитовых операторов &, ^ и | ниже, чем приоритет == и !=, из-за чего впобитовых проверках, таких какif ((х & MASK) == 0) …чтобы получить правильный результат, приходится использовать скобки.Си подобно многим языкам не фиксирует очередность вычисления операндов оператора (за исключением &&, ||, ?: и ,). Например, в инструкции видах = f() + g();f может быть вычислена раньше g или наоборот. Из этого следует, что если одна из функций изменяетзначение переменной, от которой зависит другая функция, то помещаемый в x результат может зависеть оточередности вычислений.
Чтобы обеспечить нужную последовательность вычислений, промежуточныерезультаты можно запоминать во временных переменных.Очередность вычисления аргументов функции также не определена, поэтому на разных компиляторахprintf("%d %d\n", ++n, power(2, n)); /* НЕВЕРНО */может давать несовпадающие результаты. Результат вызова функции зависит от того, когда компиляторсгенерирует команды увеличения n — до или после обращения к power.
Чтобы обезопасить себя отвозможного побочного эффекта, достаточно написать++n;printf("%d %d\n", n, power(2, n));Обращения к функциям, вложенные присвоения, инкрементные и декрементные операторы дают "побочныйэффект", проявляющийся в том, что при вычислении выражения значения некоторых переменныхизменяются. В любом выражении с побочным эффектом может быть скрыта трудно просматриваемаязависимость результата выражения от очередности изменения значений переменных, входящих ввыражение. В такой, например, типично неприятной ситуацииa[i] = i++;возникает вопрос: массив a индексируется старым или измененным значением i? Компиляторы могут поразному генерировать программу, что проявится в интерпретации данной записи. Стандарт сознательноустроен так, что большинство подобных вопросов оставлено на усмотрение компиляторов, так как лучшийпорядок вычислений определяется архитектурой машины.
Стандартом только гарантируется, что всепобочные эффекты при вычислении аргументов проявятся перед входом в функцию. Правда, в примере сprintf это нам не поможет.Мораль такова: писать программы, зависящие от очередности вычислений, — плохая практика, какой бы языквы ни использовали. Естественно, надо знать, чего следует избегать, но если вы не знаете, как образуютсяпобочные эффекты на разных машинах, то лучше и не рассчитывать выиграть на особенностях частнойреализации.3. УправлениеПорядок, в котором выполняются вычисления, определяется инструкциями управления.
Мы уже встречалисьс наиболее распространенными управляющими конструкциями такого рода в предыдущих примерах;здесьмы завершим их список и более точно определим рассмотренные ранее.3.1. Инструкции и блокиВыражение, скажем х = 0, или i++, или printf (…) , становится инструкцией, если в конце его поставитьточку с запятой, например:х = 0;i++;printf (…);В Си точка с запятой является заключающим символом инструкции, а не разделителем, как в языке Паскаль.Фигурные скобки { и } используются для объединения объявлений и инструкций в составную инструкцию,или блок, чтобы с точки зрения синтаксиса эта новая конструкция воспринималась как одна инструкция.Фигурные скобки, обрамляющие группу инструкций, образующих тело функции, — это один пример; второйпример — это скобки, объединяющие инструкции, помещенные после if, else, while или for.(Переменные могут быть объявлены внутри любого блока, об этом разговор пойдет в главе 4.) После правойзакрывающей фигурной скобки в конце блока точка с запятой не ставится.3.2.
Конструкция if-elseИнструкция if-else используется для принятия решения. Формально ее синтаксисом является:if (выражение)инструкция1elseинструкция2причем else-часть может и отсутствовать. Сначала вычисляется выражение, и, если оно истинно (т. е.отлично от нуля), выполняется инструкция1. Если выражение ложно (т. е. его значение равно нулю) исуществует else-часть, то выполняется инструкция2.Так как if просто проверяет числовое значение выражения, условие иногда можно записывать всокращенном виде. Так, записьif (выражение)короче, чемif (выражение ! = 0)Иногда такие сокращения естественны и ясны, в других случаях, наоборот, затрудняют пониманиепрограммы.Отсутствие else-части в одной из вложенных друг в друга if-конструкций может привести кнеоднозначному толкованию записи.
Эту неоднозначность разрешают тем, что else связывают с ближайшимif, у которого нет своего else. Например, вif (n > 0)if (а > Ь)z = а;elsez = b;else относится к внутреннему if, что мы и показали с помощью отступов. Если нам требуется инаяинтерпретация, необходимо должным образом расставить фигурные скобки:if (n > 0) {if (a > b)z = а;}elsez = b;Ниже приводится пример ситуации, когда неоднозначность особенно опасна:if (n >= 0)for (i = 0; i < n; i++)if (s[i] > 0) {printf ( " . . . " ) ;return i;}else /* НЕВЕРНО */printf("ошибка - отрицательное n\n");С помощью отступов мы недвусмысленно показали, что нам нужно, однако компилятор не воспримет этуинформацию и отнесет else к внутреннему if.
Искать такого рода ошибки особенно тяжело. Здесь уместенследующий совет: вложенные if обрамляйте фигурными скобками.Кстати, обратите внимание на точку с запятой после z = а вif (a > b)z = а;elsez = b;Здесь она обязательна, поскольку по правилам грамматики за if должна следовать инструкция, авыражение-инструкция вроде z = а; всегда заканчивается точкой с запятой.3.3. Конструкция else-ifКонструкцияif (выражение)инструкцияelse if (выражение)инструкцияelse if (выражение)инструкцияelse if (выражение)инструкцияelseинструкциявстречается так часто, что о ней стоит поговорить особо.
Приведенная последовательность инструкций if —самый общий способ описания многоступенчатого принятия решения. Выражения вычисляются по порядку;как только встречается выражение со значением "истина", выполняется соответствующая ему инструкция; наэтом последовательность проверок завершается. Здесь под словом инструкция имеется в виду либо однаинструкция, либо группа инструкций в фигурных скобках.Последняя else-часть срабатывает, если не выполняются все предыдущие условия. Иногда в последнейчасти не требуется производить никаких действий, в этом случае фрагментelseинструкцияможно опустить или использовать для фиксации ошибочной ("невозможной") ситуации.В качестве иллюстрации трехпутевого ветвления рассмотрим функцию бинарного поиска значения x вмассиве v. Предполагается, что элементы v упорядочены по возрастанию.
Функция выдает положение x в v(число в пределах от 0 до n-1 ), если x там встречается, и -1 , если его нет.При бинарном поиске значение x сначала сравнивается с элементом, занимающим серединное положение вмассиве v. Если x меньше, чем это значение, то областью поиска становится "верхняя" половина массива v, впротивном случае — "нижняя". В любом случае следующий шаг — это сравнение с серединным элементомотобранной половины.
Процесс "уполовинивания" диапазона продолжается до тех пор, пока либо не будетнайдено значение, либо не станет пустым диапазон поиска. Запишем функцию бинарного поиска:/* binsearch: найти х в v[0] <= v[1] <= … <= v[n-1] */int binsearch(int x, int v[], int n){int low, high, mid;low = 0;high = n - 1 ;while (low <= high) {mid = (low + high) / 2;if (x < v[mid])high = mid - 1;else if (x > v[mid])low = mid + 1 ;else /* совпадение найдено */return mid;}return -1; /* совпадения нет */}Основное действие, выполняемое на каждом шаге поиска, — сравнение значения x (меньше, больше илиравно) с элементом v[mid]; это сравнение естественно поручить конструкции else-if.Упражнение 3.1. В нашей программе бинарного поиска внутри цикла осуществляются две проверки, хотямогла быть только одна (при увеличении числа проверок вне цикла).
Напишите программу, предусмотрев вней одну проверку внутри цикла. Оцените разницу во времени выполнения.3.4. Переключатель switchИнструкция switch используется для выбора одного из многих путей. Она проверяет, совпадает ли значениевыражения с одним из значений, входящих в некоторое множество целых констант, и выполняетсоответствующую этому значению ветвь программы:switch (выражение) {case конст-выр: инструкцииcase конст-выр: инструкцииdefault: инструкции}Каждая ветвь case помечена одной или несколькими целочисленными константами или же константнымивыражениями. Вычисления начинаются с той ветви case, в которой константа совпадает со значениемвыражения.















