Керниган и Ритчи - Язык программирования Си (793773), страница 18
Текст из файла (страница 18)
Константы всех ветвей case должны отличаться друг от друга. Если выяснилось, что ни одна изконстант не подходит, то выполняется ветвь, помеченная словом default, если таковая имеется, впротивном случае ничего не делается. Ветви case и default можно располагать в любом порядке.В главе 1 мы написали программу, подсчитывающую число вхождений в текст каждой цифры, символовразделителей (пробелов, табуляций и новых строк) и всех остальных символов.
В ней мы использовалипоследовательность if…else if…else. Теперь приведем вариант этой программы с переключателемswitch:#include <stdio.h>main() /* подсчет цифр, символов-разделителей и прочих символов */{int с, i, nwhite, nother, ndigit[10];nwhite = nother = 0;for (i = 0; i < 10; i++)ndigit[i] = 0;while ((c = getcharO) != EOF) {switch (c) {case '0': case '1': case '2': case '3': case '4':case '5': case '6': case '7': case '8': case '9':ndigit[c - '0' ]++;break;case ' ':case '\n':case '\t':nwhite++;break;default:nother++;break;}}printf ("цифр =");for (i= 0; i < 10; i++)printf (" %d", ndigit[i]);printf (", символов-разделителей = %d, прочих = %d\n", nwhite, nother);return 0;}Инструкция break вызывает немедленный выход из переключателя switch. Поскольку выбор ветви caseреализуется как переход на метку, то после выполнения одной ветви case, если ничего не предпринять,программа провалится вниз на следующую ветвь. Инструкции break и return — наиболеераспространенные средства выхода из переключателя.
Инструкция break используется также дляпринудительного выхода из циклов while, for и do-while (мы еще поговорим об этом чуть позже)."Сквозное" выполнение ветвей case вызывает смешанные чувства. С одной стороны, это хорошо, посколькупозволяет несколько ветвей case объединить в одну, как мы и поступили с цифрами в нашем примере. Но сдругой это означает, что в конце почти каждой ветви придется ставить break, чтобы избежать перехода кследующей. Последовательный проход по ветвям — вещь ненадежная, это чревато ошибками, особенно приизменении программы.
За исключением случая с несколькими метками для одного вычисления, старайтесьпо возможности реже пользоваться сквозным проходом, но если уж вы его применяете, обязательнокомментируйте эти особые места.Добрый вам совет: даже в конце последней ветви (после default в нашем примере) помещайте инструкциюbreak, хотя с точки зрения логики в ней нет никакой необходимости. Но эта маленькая предосторожностьспасет вас, когда однажды вам потребуется добавить в конец еще одну ветвь case.Упражнение 3.2. Напишите функцию escape(s, t), которая при копировании текста из t в s преобразуеттакие символы, как новая строка и табуляция в "видимые последовательности символов" (вроде \n и \t).Используйте инструкцию switch.
Напишите функцию, выполняющую обратное преобразование эскейппоследовательностей в настоящие символы.3.5. Циклы while и forМы уже встречались с циклами while и for. В циклеwhile (выражение)инструкциявычисляется выражение. Если его значение отлично от нуля, то выполняется инструкция, и вычислениевыражения повторяется. Этот цикл продолжается до тех пор, пока выражение не станет равным нулю, послечего вычисления продолжатся с точки, расположенной сразу за инструкцией.Инструкция forfor (выр1; выр2; выр3)инструкцияэквивалентна конструкциивыр1;while (выр2) {инструкциявыр3;}если не считать отличий в поведении инструкции continue, речь о которой пойдет в параграфе 3. 7.С точки зрения грамматики три компоненты цикла for представляют собой произвольные выражения, ночаще выр1 и выр3 — это присваивания или вызовы функций, а выр2 — выражение отношения. Любое из этихтрех выражений может отсутствовать, но точку с запятой опускать нельзя.
При отсутствии выр1 или выр3считается, что их просто нет в конструкции цикла; при отсутствии выр2 предполагается, что его значение какбы всегда истинно. Например,for (;;) {…}есть "бесконечный" цикл, выполнение которого, вероятно, прерывается каким-то другим способом, напримерс помощью инструкций break или return.Какой цикл выбрать: while или for — это дело вкуса. Так, вwhile ((с = getchar()) == ' ' || с == '\n' || с == '\t'); /* обойти символы-разделители */нет ни инициализации, ни пересчета параметра, поэтому здесь больше подходит while.Там, где есть простая инициализация и пошаговое увеличение значения некоторой переменной, большеподходит цикл for, так как в этом цикле организующая его часть сосредоточена в начале записи.
Например,начало цикла, обрабатывающего первые n элементов массива, имеет следующий вид:for (i = 0; i < n; i++)…Это похоже на DO-циклы в Фортране и for-циклы в Паскале. Сходство, однако, не вполне точное, так как в Сииндекс и его предельное значение могут изменяться внутри цикла, и значение индекса i после выхода изцикла всегда определено. Поскольку три компоненты цикла могут быть произвольными выражениями,организация fоr-циклов не ограничивается только случаем арифметической прогрессии. Однако включать взаголовок цикла вычисления, не имеющие отношения к инициализации и инкрементированию, считаетсяплохим стилем. Заголовок лучше оставить только для операций управления циклом.В качестве более внушительного примера приведем другую версию программы atoi, выполняющейпреобразование строки в ее числовой эквивалент.
Это более общая версия по сравнению с рассмотренной вглаве 2, в том смысле, что она игнорирует левые символы-разделители (если они есть) и должным образомреагирует на знаки + и -, которые могут стоять перед цифрами. (В главе 4 будет рассмотрен вариант atof,который осуществляет подобное преобразование для чисел с плавающей точкой.)Структура программы отражает вид вводимой информации:игнорировать символы-разделители, если они естьполучить знак, если он естьвзять целую часть и преобразовать ееНа каждом шаге выполняется определенная часть работы и четко фиксируется ее результат, который затемиспользуется на следующем шаге.
Обработка данных заканчивается на первом же символе, который неможет быть частью числа.#include <ctype.h>/* atoi: преобразование s в целое число; версия 2 */int atoi(char s[]){int i, n, sign;/* игнорировать символы-разделители */for (i = 0; isspace(s[i]); i++);sign = (s[i] == '-') ? -1: 1;if (s[i] == '+' || s[i] == '-') /* пропуск знака */i++;for (n = 0; isdigit(s[i]); i++)n = 10 * n + (s[i] - '0' );return sign * n;}Заметим, что в стандартной библиотеке имеется более совершенная функция преобразования строки вдлинное целое (long int) — функция strtol (см.
параграф 5 приложения В).Преимущества, которые дает централизация управления циклом, становятся еще более очевидными, когданесколько циклов вложены друг в друга. Проиллюстрируем их на примере сортировки массива целых чиселметодом Шелла, предложенным им в 1959 г. Основная идея этого алгоритма в том, что на ранних стадияхсравниваются далеко отстоящие друг от друга, а не соседние элементы, как в обычных перестановочныхсортировках. Это приводит к быстрому устранению массовой неупорядоченности, благодаря чему на болеепоздней стадии остается меньше работы.
Интервал между сравниваемыми элементами постепенноуменьшается до единицы, и в этот момент сортировка сводится к обычным перестановкам соседнихэлементов. Программа shellsort имеет следующий вид:/* shellsort: сортируются v[0] ... v[n-1] в возрастающем порядке */void shellsort (int v[], int n){int gap, i, j, temp;for (gap = n/2; gap > 0; gap /= 2)for (i = gap; i < n; i++)for (j = i - gap; j >= 0 && v[j] > v[j + gap]; j -= gap) {temp = v[j];v[j] = v[j + gap];v[j + gap] = temp;}}Здесь использованы три вложенных друг в друга цикла.
Внешний управляет интервалом gap междусравниваемыми элементами, сокращая его путем деления пополам от n/2 до нуля. Средний цикл перебираетэлементы. Внутренний — сравнивает каждую пару элементов, отстоящих друг от друга на расстоянии gap, ипереставляет элементы в неупорядоченных парах. Так как gap обязательно сведется к единице, все элементыв конечном счете будут упорядочены. Обратите внимание на то, что универсальность цикла for позволяетсделать внешний цикл по форме похожим на другие, хотя он и не является арифметической прогрессией.Последний оператор Си — это "," (запятая), которую чаще всего используют в инструкции for.
Паравыражений, разделенных запятой, вычисляется слева направо. Типом и значением результата являются тип изначение правого выражения, что позволяет в инструкции for в каждой из трех компонент иметь понескольку выражений, например вести два индекса параллельно. Продемонстрируем это на примерефункции reverse(s), которая "переворачивает" строку s, оставляя результат в той же строке s:#include <string.h>/* reverse: переворачивает строку s (результат в s) */void reverse(char s[]){int c, i, j;for (i = 0, j = strlen(s)-1; i < j; i++, j--) {с = s[i];s[i] = s[j];s[j] = c;}}Запятые, разделяющие аргументы функции, переменные в объявлениях и пр. не являются операторамизапятыми и не обеспечивают вычислений слева направо.Запятыми как операторами следует пользоваться умеренно. Более всего они уместны в конструкциях,которые тесно связаны друг с другом (как в for-цикле программы reverse), а также в макросах, в которыхмногоступенчатые вычисления должны быть выражены одним выражением.















