Главная страница
Навигация по странице:

  • 86 _ _ Глава 3. Управление

  • 3. Управление 3.7. Инструкции

  • 3.8. Инструкция goto и метки

  • Функции и структура программы

  • 94 Глава 4. Функции и структура программы

  • Б. Керриган, Д. Ритчи Язык программирования C. Б. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003


    Скачать 31.48 Mb.
    НазваниеБ. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003
    АнкорБ. Керриган, Д. Ритчи Язык программирования C.pdf
    Дата06.04.2017
    Размер31.48 Mb.
    Формат файлаpdf
    Имя файлаБ. Керриган, Д. Ритчи Язык программирования C.pdf
    ТипКнига
    #4546
    страница8 из 28
    1   ...   4   5   6   7   8   9   10   11   ...   28
    Упражнение 3.2. Напишите escape(s, t), которая при копи- ровании текста из t в s преобразует такие символы, как новая строка и табуляция в "видимые последовательности символов" (вроде \л и \t).
    Используйте инструкцию switch. Напишите функцию, выполняющую обратное преобразование эскейп-последовательностей в настоящие символы.
    3.5. Циклы while и for
    Мы уже встречались с циклами while и for. В цикле
    (выражение)
    инструкция
    вычисляется выражение. Если его значение отлично от нуля, то выполня- ется инструкция, и вычисление выражения повторяется. Этот цикл про- должается до тех пор, пока выражение не станет равным нулю, после чего вычисления продолжатся с точки, расположенной сразу за инструкцией.
    Инструкция for for
    инструкция
    эквивалентна конструкции while
    {
    инструкция
    если не считать отличий в поведении инструкции continue, речь о кото- рой пойдет в параграфе 3. 7.
    С точки зрения грамматики три компоненты цикла for представляют собой произвольные выражения, но чаще и
    это присваивания или вызовы функций, а
    - выражение отношения.
    из этих трех

    3.5. Циклы while и for _ _ 85
    выражений может отсутствовать, но точку с запятой опускать нельзя. При отсутствии или считается, что их просто нет в конструкции цик- ла; при отсутствии предполагается, что его значение как бы всегда истинно. Например,
    for
    (;;)
    {
    есть "бесконечный" цикл, выполнение которого, вероятно, прерывается каким-то другим способом, например с помощью инструкций Ь reak или return.
    Какой цикл выбрать: while или f o r - это дело вкуса. Так, в while
    =
    == ' ''
    с ==
    с ==
    ; /* обойти символы-разделители */
    нет ни инициализации, ни пересчета параметра, поэтому здесь больше подходит while.
    Там, где есть простая инициализация и пошаговое увеличение значе- ния некоторой переменной, больше подходит цикл так как в этом цик- ле организующая его часть сосредоточена в начале записи.
    на- чало обрабатывающего первые п элементов массива, имеет следу- ющий вид:
    for (i = 0; i <
    Это похоже на в Фортране и в
    Сходство,
    однако, не вполне точное, так как в Си индекс и его предельное значение могут изменяться внутри цикла, и значение индекса i выхода из цикла всегда определено. Поскольку три компоненты цикла могут быть произвольными выражениями, организация f о не ограничива- ется только случаем арифметической прогрессии. Однако включать в за- головок цикла вычисления, не имеющие отношения к инициализации и инкрементированию, считается плохим стилем. Заголовок лучше оста- вить только для операций управления циклом.
    В качестве более внушительного примера приведем другую версию про- граммы at выполняющей преобразование строки в ее числовой эквива- лент. Это более общая версия по сравнению с рассмотренной в главе 2,
    в том смысле, что она игнорирует левые символы-разделители (если они есть) и должным образом реагирует на знаки + и -, которые могут стоять перед цифрами. (В главе 4 будет рассмотрен вариант atof, который осу- ществляет подобное преобразование для чисел с плавающей точкой.)

    86 _ _ Глава 3. Управление
    Структура программы отражает вид вводимой информации:
    символы-разделители, если они
    знак, если он
    целую часть и
    ее
    На каждом шаге выполняется определенная часть работы и четко фикси- руется ее результат, который затем используется на следующем шаге.
    Обработка данных заканчивается на первом же символе, который не мо- жет быть частью числа.
    ttinclude
    /*
    преобразование s в целое число; версия 2 */
    int i, n, sign;
    /* игнорировать символы-разделители */
    for (i = 0;
    sign = (s[i] == '-') ? -1: 1;
    if (s[i] == '+'
    s[i] ==
    /* пропуск знака */
    for (n = 0;
    i++)
    n = 10 * n + (s[i] - return sign * n;
    }
    Заметим, что в стандартной библиотеке имеется более совершенная фун- кция преобразования строки в длинное целое (long int) - функция st
    (см. параграф 5 приложения В).
    Преимущества, которые дает централизация управления циклом, ста- новятся еще более очевидными, когда несколько циклов вложены друг в друга. Проиллюстрируем их на примере сортировки массива целых чи- сел методом Шелла, предложенным им в г. Основная идея этого алго- ритма в том, что на ранних стадиях сравниваются далеко отстоящие друг от друга, а не соседние элементы, как в обычных перестановочных сорти- ровках. Это приводит к быстрому устранению массовой неупорядоченно- сти, благодаря чему на более поздней стадии остается меньше работы. Ин- тервал между сравниваемыми элементами постепенно уменьшается до еди- ницы, и в этот момент сортировка сводится к обычным перестановкам со- седних элементов. Программа shellsort имеет следующий вид:
    /* shellsort:
    v v
    в возрастающем порядке */
    void shellsort (int v [], int n)

    3.5. Циклы while и for __ 87
    int gap,
    temp;
    for
    (gap
    = n/2; gap > 0; gap /= 2)
    (i
    = gap; i < n; i++)
    for (j
    = i - gap; j >= 0 && v[j] > v[j +
    j -= gap) {
    temp =
    v[j]
    = v[j v[j + gap] = temp;
    Здесь использованы три вложенных друг в друга цикла. Внешний управ- ляет интервалом gap между сравниваемыми элементами, сокращая путем деления пополам от п/2 до нуля. Средний цикл перебирает элемен- ты. Внутренний - сравнивает каждую пару элементов, отстоящих друг от друга на расстоянии gap, и переставляет элементы в неупорядоченных парах. Так как gap обязательно сведется к единице, все элементы в конеч- ном счете будут упорядочены. Обратите внимание на то, что универсаль- ность цикла f o r позволяет сделать внешний цикл по форме похожим на другие, хотя он и не является арифметической прогрессией.
    Последний оператор Си - это
    (запятая), которую чаще всего ис- пользуют в инструкции f о г. Пара выражений, разделенных запятой, вы- числяется слева направо. Типом и значением результата являются тип и значение правого выражения, что позволяет в инструкции в каждой из трех компонент иметь по нескольку выражений, например вести два индекса параллельно. Продемонстрируем это на примере функции которая "переворачивает" строку s, оставляя результат в той же строке s:
    /* reverse: переворачивает строку s (результат в s) */
    void reverse(char
    {
    int i,
    for (i = 0, j = strlen(s)-1; i < j; i++, j
    {
    с =
    s[j]
    = c;

    3. Управление
    Запятые, разделяющие аргументы функции, переменные в объявлениях и пр. не являются операторами-запятыми и не обеспечивают вычислений слева направо.
    Запятыми как операторами следует пользоваться умеренно. Более всего они уместны в конструкциях, которые тесно связаны друг с другом (как в
    программы reverse), а также в макросах, в которых многосту- пенчатые вычисления должны быть выражены одним выражением. За- пятой-оператором в программе можно было бы воспользоваться и при обмене символами в проверяемых парах элементов строки, мысля этот обмен как одну отдельную операцию:
    for (i = 0, j = strlen(s)-1; i < j; i++,
    с = s[i], s[i] = s[j], s[j] = c;
    Упражнение 3.3. Напишите функцию s2), заменяющую сокращенную запись наподобие в строке s1 эквивалентной полной записью
    . . xyz в s2. В s1 допускаются буквы (прописные и строчные)
    и цифры. Следует уметь справляться с такими случаями, как a-b-c,
    -9 и
    Считайте знак - в начале или в конце s1 обычным символом минус.
    3.6. Цикл do-while
    Как мы говорили в главе в циклах
    1е и f о проверка условия окон- чания цикла выполняется наверху. В Си имеется еще один вид цикла,
    while, в котором эта проверка в отличие от while и f o r делается внизу
    после каждого прохождения тела цикла, т. е. после того, как тело вы- полнится хотя бы один раз. Цикл do-while имеет следующий синтаксис:
    do
    инструкция
    while (выражение);
    Сначала выполняется инструкция, затем вычисляется выражение. Если оно истинно, то инструкция выполняется снова и т. д. Когда выражение становится ложным, цикл заканчивает работу. Цикл эквивален- тен циклу в Паскале с той лишь разницей, что в первом чае указывается условие продолжения цикла, а во втором условие его окончания.
    Опыт показывает, что цикл do-while используется гораздо реже, чем while и f o r . Тем не менее потребность в нем время от времени возникает,
    как, например, в функции
    (обратной по отношению к atoi), преоб-

    3.6. Цикл
    _
    _
    разующей число в строку символов. Выполнить такое преобразование оказалось несколько более сложным делом, чем ожидалось, поскольку простые алгоритмы генерируют цифры в обратном порядке. Мы остано- вились на варианте, в котором сначала формируется обратная последова- тельность цифр, а затем она реверсируется.
    /*
    преобразование в строку s */
    void itoa(int n,
    {
    int i, sign;
    if
    = n) < 0) /* сохраняем знак */
    n = -n; /* делаем n положительным */
    i = 0;
    do { /* генерируем цифры в обратном порядке */
    s[i++] = n % 10 +
    /* следующая цифра */
    } while
    /= 10) >
    /* исключить ее */
    if (sign < 0)
    s[i++] =
    s[i] =
    Конструкция do-while здесь необходима или по крайней мере удобна,
    поскольку в s посылается хотя бы один символ, даже если n равно нулю.
    В теле цикла одну инструкцию мы выделили фигурными скобками (хотя они и избыточны), чтобы неискушенный читатель не принял по ошибке слово while за начало цикла while.
    Упражнение 3.4. При условии, что для представления чисел используется дополнительный код, наша версия itoa не справляется с самым большим по модулю отрицательным числом, значение которого равняется
    ),
    где п - размер слова. Объясните, чем это вызвано. Модифицируйте программу таким образом, чтобы она давала правильное значение указанного числа независимо от машины, на которой выполняется.
    Упражнение 3.5. Напишите функцию которая переводит целое п в строку s, представляющую число по основанию В частности,
    16) помещает в s текст числа п в шестнадцатеричном виде.
    Упражнение 3.6. Напишите версию itoa с дополнительным третьим аргументом, задающим минимальную ширину поля. При необходимости преобразованное число должно слева дополняться пробелами.

    3. Управление
    3.7. Инструкции break и continue
    Иногда бывает удобно выйти из цикла не по результату проверки, осу- ществляемой в начале или в конце цикла, а каким-то другим способом.
    Такую возможность для циклов f o r , while и do-while, а также для пере- ключателя switch предоставляет инструкция
    Эта инструкция вы- зывает немедленный выход из самого внутреннего из объемлющих ее циклов или переключателей.
    Следующая функция, t rim, удаляет из строки завершающие пробелы,
    табуляции, символы новой строки;
    используется в ней для выхода из цикла по первому обнаруженному справа символу, отличному от на- званных.
    /* trim: удаляет завершающие пробелы, табуляции и новые строки */
    int
    {
    int n;
    for (n = strlen(s)-1; n >= 0;
    if (s[n]
    s[n] !=
    && s[n] !=
    break;
    s[n+1] =
    return n;
    }
    С помощью функции можно получить длину строки. Цикл for просматривает его в обратном порядке, начиная с конца, до тех пор, пока не встретится символ, отличный от пробела, табуляции и новой строки.
    Цикл прерывается, как только такой символ обнаружится или n станет отрицательным (т. е. вся строка будет просмотрена). Убедитесь, что функ- ция ведет себя правильно и в случаях, когда строка пуста или состоит толь- ко из символов-разделителей.
    Инструкция continue в чем-то похожа на break, но применяется го- раздо реже. Она вынуждает ближайший объемлющий ее цикл while или do-while) начать следующий шаг итерации. Для while и do-while это означает немедленный переход к проверке условия, а для
    - к прира- щению шага. Инструкцию continue можно применять только к циклам,
    но не к switch. Внутри переключателя switch, расположенного в цикле,
    она вызовет переход к следующей итерации этого цикла.
    Вот фрагмент программы, обрабатывающий только неотрицательные элементы массива а (отрицательные пропускаются).

    3.8. Инструкция goto и метки for i < n; i++) {
    if (a[i] < 0) /* пропуск отрицательных элементов */
    continue;
    /* обработка положительных элементов */
    }
    К инструкции conti часто прибегают тогда, когда оставшаяся часть цикла сложна, а замена условия в нем на противоположное и введение еще одно- го уровня приводят к слишком большому числу уровней вложенности.
    3.8. Инструкция goto и метки
    В Си имеются порицаемая многими инструкция goto и метки для пере- хода на них. Строго говоря, в этой инструкции нет никакой необходимо- сти, и на практике почти всегда легко без нее обойтись. До сих пор в на- шей книге мы не использовали goto.
    Однако существуют случаи, в которых goto может пригодиться. Наи- более типична ситуация, когда нужно прервать обработку в некоторой глубоко вложенной структуре и выйти сразу из двух или большего числа вложенных циклов. Инструкция здесь не поможет, так как она обе- спечит выход только из самого внутреннего цикла. В качестве примера рассмотрим следующую конструкцию:
    for (...)
    {
    if (disaster) /* если бедствие */
    goto error; /* уйти на ошибку */
    error: /* обработка ошибки */
    Такая организация программы удобна, если подпрограмма обработки ошибочной ситуации не тривиальна и ошибка может встретиться в не- скольких местах.
    Метка имеет вид обычного имени переменной, за которым следует дво- еточие. На метку можно перейти с помощью goto из любого места данной функции, т. е. метка видима на протяжении всей функции.
    В качестве еще одного примера рассмотрим такую задачу: определить,
    есть ли в массивах а и b совпадающие элементы. Один из возможных ва- риантов ее реализации имеет следующий вид:

    92 Глава 3. Управление
    for i < n;
    for (j
    = 0; j <
    j++)
    if
    (a[i] ==
    goto found;
    /* нет одинаковых элементов */
    found:
    /* обнаружено совпадение: a[i] == b[j] */
    Программу нахождения совпадающих элементов можно написать и без goto, правда, заплатив за это дополнительными проверками и еще одной переменной:
    found = 0;
    for (i = 0; i < n &&
    i for (j = 0; j < m &&
    if (a[i] ==
    found =
    if (found)
    /* обнаружено
    ==
    */
    - else
    /* нет одинаковых элементов */
    За исключением редких случаев, подобных только что приведенным,
    программы с применением goto, как правило, труднее для понимания и сопровождения, чем программы, решающие те же задачи без goto. Хотя мы и не догматики в данном вопросе, все же думается, что к goto следует прибегать крайне редко, если использовать эту инструкцию вообще.

    Глава 4
    Функции
    и структура программы
    Функции разбивают большие вычислительные задачи на более мелкие и позволяют воспользоваться тем, что уже сделано другими разработчи- ками, а не начинать создание программы каждый раз "с нуля". В выбран- ных должным образом функциях "упрятаны" несущественные для дру- гих частей программы детали их функционирования, что делает програм- му в целом более ясной и облегчает внесение в нее изменений.
    Язык проектировался чтобы функции были эффективными и про- стыми в использовании. Обычно программы на Си состоят из большого числа небольших функций, а не из немногих больших. Программу можно располагать в одном или нескольких исходных файлах. Эти файлы мож- но компилировать отдельно, а загружать вместе, в том числе и с ранее откомпилированными библиотечными функциями. Процесс загрузки здесь не рассматривается, поскольку он различен в разных системах.
    Объявление и определение функции - это та область, где стандартом
    ANSI в язык внесены самые существенные изменения. Как мы видели в главе 1, в описании функции теперь разрешено задавать типы аргумен- тов. Синтаксис определения функции также изменен, так что теперь объявления и определения функций соответствуют друг другу. Это по- зволяет компилятору обнаруживать намного больше ошибок, чем рань- ше. Кроме того, если типы аргументов соответствующим образом объяв- лены, то необходимые преобразования аргументов выполняются автомати- чески.
    Стандарт вносит ясность в правила, определяющие области видимос- ти имен; в частности, он требует, чтобы для каждого внешнего объекта было только одно определение. В нем обобщены средства инициализа- ции: теперь можно инициализировать автоматические массивы и струк- туры.

    94 Глава 4. Функции и структура программы
    Улучшен также препроцессор Си. Он включает более широкий набор директив условной компиляции, предоставляет возможность из макро- аргументов генерировать строки в кавычках, а кроме того, содержит бо- лее совершенный механизм управления процессом макрорасширения.
    Основные сведения о функциях
    Начнем с того, что сконструируем программу, печатающую те строки вводимого текста, в которых содержится некоторый "образец", заданный в виде строки символов.
    программа представляет собой частный слу- чай функции системы UNIX.) Рассмотрим пример: в результате по- иска образца в строках текста
    Ah Love! could you and I with Fate conspire
    To grasp this sorry Scheme of Things entire,
    Would not we shatter it to bits — and then
    Re-mould it nearer to the Heart's Desire!
    мы получим
    Ah Love! could you and I with Fate conspire
    Would not we shatter it to bits — and then
    Re-mould it nearer to the Heart's Desire!
    Работа по поиску образца четко распадается на три этапа:
    while
    еще строка)
    f
    содержит
    напечатать ее
    Хотя три составляющие процесса поиска можно поместить в функ- цию main,
    же лучше сохранить приведенную структуру и каждую ее часть реализовать в виде отдельной функции. Легче иметь дело с тремя небольшими частями, чем с одной большой, поскольку, если несуществен- ные особенности реализации скрыты в функциях, вероятность их неже- лательного воздействия друг на друга минимальна. Кроме того, оформ- ленные в виде функций соответствующие части могут оказаться полез- ными и в других программах.
    Конструкция "while (существует еще строка)" реализована в getline
    (см. главу а фразу
    ее" можно записать с помощью гото- вой функции
    Таким образом, нам остается перевести на Си толь- ко то, что определяет, входит ли заданный образец в

    1   ...   4   5   6   7   8   9   10   11   ...   28


    написать администратору сайта