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

  • 3.9. Оператор GOTO и метки

  • 4. ФУНКЦИИ И СТРУКТУРА ПРОГРАММ.

  • 4.2. Функции, возвращающие нецелые значения.

  • Язык С (Керниган, Ричи). Язык сиБ. В. Керниган, Д. М. Ричи


    Скачать 1.46 Mb.
    НазваниеЯзык сиБ. В. Керниган, Д. М. Ричи
    АнкорЯзык С (Керниган, Ричи).pdf
    Дата23.04.2018
    Размер1.46 Mb.
    Формат файлаpdf
    Имя файлаЯзык С (Керниган, Ричи).pdf
    ТипДокументы
    #18413
    страница8 из 23
    1   ...   4   5   6   7   8   9   10   11   ...   23
    Упражнение 3-2.
    Составьте программу для функции EXPAND(S1,S2), которая расширяет сокращенные обозначения вида а-Z из строки S1 в эквивалентный полный список авс...XYZ в S2. Допускаются сокращения для строчных и прописных букв и цифр. Будьте готовы иметь дело со случаями типа а-в-с, а-Z0-9 и -а-Z.
    (Полезное соглашение состоит в том, что символ -, стоящий в начале или конце, воспринимается буквально).
    3.6. Цикл DO - WHILE
    Как уже отмечалось в главе 1, циклы WHILE и FOR обладают тем приятным свойством, что в них проверка окончания осуществляется в начале,
    а не в конце цикла. Третий оператор цикла языка “C”, DO-WHILE, проверяет условие окончания в конце, после каждого прохода через тело цикла; тело цикла всегда выполняется по крайней мере один раз. Синтаксис этого опера- тора имеет вид:
    DO
    оператор
    WHILE (выражение)
    Сначала выполняется оператор, затем вычисляется выражение.
    Если оно истинно, то оператор выполняется снова и т.д. Если выражение становится ложным, цикл заканчивается.
    Как и можно было ожидать, цикл DO-WHILE используется значительно реже, чем WHILE и FOR, составляя примерно пять процентов от всех циклов.
    Тем не менее, иногда он оказывается полезным, как, например, в следующей функции ITOA, которая преобразует число в символьную строку (обратная функции ATOI). Эта задача оказывается несколько более сложной, чем может показаться сначала. Дело в том, что простые методы выделения цифр генерируют их в неправильном порядке. Мы предпочли получить строку в обратном порядке, а затем обратить ее.
    ITOA(N,S) /*CONVERT N TO CHARACTERS IN S */
    CHAR S[];
    INT N;
    INT I, SIGN;
    IF ((SIGN = N) < 0) /* RECORD SIGN */
    N = -N;
    /* MAKE N POSITIVE */
    I = 0;
    DO /* GENERATE DIGITS IN REVERSE ORDER */

    68
    «Язык С» Б.В. Керниган, Д.М. Ричи
    S[I++] = N % 10 + ‘0’;/* GET NEXT DIGIT */ WHILE
    ((N /=10) > 0); /* DELETE IT */ IF (SIGN < 0)
    S[I++] = ‘-’
    S[I] = ‘\0’;
    REVERSE(S);
    Цикл DO-WHILE здесь необходим, или по крайней мере удобен, поскольку,
    каково бы ни было значение N, массив S должен содержать хотя бы один символ.
    Мы заключили в фигурные скобки один оператор, составляющий тело DO-
    WHILе, хотя это и не обязательно, для того, чтобы торопливый читатель не принял часть WHILE за начало оператора цикла WHILE.
    Упражнение 3-3.
    При представлении чисел в двоичном дополнительном коде наш вариант
    ITOA не справляется с наибольшим отрицательным числом, т.е. Со значением
    N рAвным -2 в степени м-1, где м - размер слова. объясните почему. Измените программу так, чтобы она правильно печатала это значение на любой машине.
    Упражнение 3-4.
    Напишите аналогичную функцию ITOB(N,S), которая преобразует целое без знака N в его двоичное символьное представление в S. Запрограммируйте функцию ITOH, которая преобразует целое в шестнадцатеричное представление.
    Упражнение 3-5.
    Напишите вариант Iтоа, который имеет три, а не два аргумента. Третий аргумент - минимальная ширина поля; преобразованное число должно, если это необходимо, дополняться слева пробелами, так чтобы оно имело достаточную ширину.
    3.7. Оператор BREAK
    Иногда бывает удобным иметь возможность управлять выходом из цикла иначе, чем проверкой условия в начале или в конце. Оператор BRеак позволяет выйти из операторов FOR, WHILE и DO до окончания цикла точно так же, как и из переключателя. Оператор BRеак приводит к немедленному выходу из самого внутреннего охватывающего его цикла (или переключателя).
    Следующая программа удаляет хвостовые пробелы и табуляции из конца каждой строки файла ввода. Она использует оператор BRеак для выхода из цикла, когда найден крайний правый отличный от пробела и табуляции символ.
    #DEFINE MAXLINE 1000
    MAIN()
    /* REMOVE TRAILING BLANKS AND TABS */
    INT N;
    CHAR LINE[MAXLINE];
    WHILE ((N = GETLINE(LINE,MAXLINE)) > 0)

    «Язык С» Б.В. Керниган, Д.М. Ричи
    69
    WHILE (—N >= 0)
    IF (LINE[N] != ‘ ‘ && LINE[N] != ‘\T’
    && LINE[N] != ‘\N’)
    BREAK;
    LINE[N+1] = ‘\0’;
    PRINTF(“%S\N”,LINE);
    Функция GETLINE возвращает длину строки. Внутренний цикл начинается с последнего символа LINE (напомним, что —N уменьшает N до использования его значения) и движется в обратном направлении в поиске первого символа , который отличен от пробела, табуляции или новой строки.
    Цикл прерывается, когда либо найден такой символ, либо N становится отри- цательным (т.е., когда просмотрена вся строка). Советуем вам убедиться, что такое поведение правильно и в том случае, когда строка состоит только из символов пустых промежутков.
    В качестве альтернативы к BRеак можно ввести проверку в сам цикл:
    WHILE ((N = GETLINE(LINE,MAXLINE)) > 0)
    WHILE (—N >= 0
    && (LINE[N] == ‘ ‘ \!\! LINE[N] == ‘\T’
    \!\! LINE[N] == ‘\N’))
    ;
    Это уступает предыдущему варианту, так как проверка становится труднее для понимания. Проверок, которые требуют переплетения &&, \!\!, ! И
    круглых скобок, по возможности следует избегать.
    3.8. Оператор CONTINUE
    Оператор CONTINUE родственен оператору BRеак, но используется реже;
    он приводит к началу следующей итерации охватывающего цикла (FOR,
    WHILE, DO ). В циклах WHILE и DO это означает непосредственный переход к выполнению проверочной части; в цикле FOR управление передается на шаг реинициализации. (Оператор CONTINUE применяется только в циклах,
    но не в переключателях. Оператор CONTINUE внутри переключателя внутри цикла вызывает выполнение следующей итерации цикла).
    В качестве примера приведем фрагмент, который обрабатывает только положительные элементы массива а; отрицательные значения пропускаются.
    FOR (I = 0; I < N; I++)
    IF (A[I] < 0) /* SKIP NEGATIVE ELEMENTS */
    CONTINUE;
    ... /* DO POSITIVE ELEMENTS */
    Оператор CONTINUE часто используется, когда последующая часть цикла

    70
    «Язык С» Б.В. Керниган, Д.М. Ричи
    оказывается слишком сложной, так что рассмотрение условия, обратного проверяемому, приводит к слишком глубокому уровню вложенности программы.
    Упражнение 3-6.
    Напишите программу копирования ввода на вывод, с тем исключением,
    что из каждой группы последовательных одинаковых строк выводится только одна. (Это простой вариант утилиты UNIQ систем UNIX).
    3.9. Оператор GOTO и метки
    В языке “C” предусмотрен и оператор GOTO, которым бесконечно злоупотребляют, и метки для ветвления. С формальной точки зрения оператор
    GOTO никогда не является необходимым, и на практике почти всегда можно обойтись без него. Мы не использовали GOTO в этой книге.
    Тем не менее, мы укажем несколько ситуаций, где оператор GOTO может найти свое место. Наиболее характерным является его использование тогда,
    когда нужно прервать выполнение в некоторой глубоко вложенной структуре,
    например, выйти сразу из двух циклов. Здесь нельзя непосредственно использовать оператор BRеак, так как он прерывает только самый внутренний цикл. Поэтому:
    FOR ( ... )
    FOR ( ... )
    IF (DISASTER)
    GOTO ERROR;
    ERROR:
    CLEAN UP THE MESS
    Если программа обработки ошибок нетривиальна и ошибки могут возникать в нескольких местах, то такая организация оказывается удобной.
    Метка имеет такую же форму, что и имя переменной, и за ней всегда следует двоеточие. Метка может быть приписана к любому оператору той же функции,
    в которой находится оператор GOTO.
    В качестве другого примера рассмотрим задачу нахождения первого отрицательного элемента в двумерном массиве. (Многомерные массивы рассматриваются в главе 5). Вот одна из возможностей:
    FOR (I = 0; I < N; I++)
    FOR (J = 0; J < M; J++)
    IF (V[I][J] < 0)
    GOTO FOUND;

    «Язык С» Б.В. Керниган, Д.М. Ричи
    71
    /* DIDN’T FIND */
    FOUND:
    /* FOUND ONE AT POSITION I, J */
    Программа, использующая оператор GOTO, всегда может быть написана без него, хотя, возможно, за счет повторения некоторых проверок и введения дополнительных переменных. Например, программа поиска в массиве примет вид:
    FOUND = 0;
    FOR (I = 0; I < N && !FOUND; I++)
    FOR (J = 0; J < M && !FOUND; J++)
    FOUND = V[I][J] < 0;
    IF (FOUND)
    /* IT WAS AT I-1, J-1 */
    ELSE
    /* NOT FOUND */
    Хотя мы не являемся в этом вопросе догматиками, нам все же кажется,
    что если и нужно использовать оператор GOTO, то весьма умеренно.

    72
    «Язык С» Б.В. Керниган, Д.М. Ричи
    4. ФУНКЦИИ И СТРУКТУРА ПРОГРАММ.
    Функции разбивают большие вычислительные задачи на маленькие подзадачи и позволяют использовать в работе то, что уже сделано другими,
    а не начинать каждый раз с пустого места. Соответствующие функции часто могут скрывать в себе детали проводимых в разных частях программы операций, знать которые нет необходимости, проясняя тем самым всю программу, как целое, и облегчая мучения при внесении изменений.
    Язык “C” разрабатывался со стремлением сделать функции эффективными и удобными для использования; “C”-программы обычно состоят из большого числа маленьких функций, а не из нескольких больших.
    Программа может размещаться в одном или нескольких исходных файлах любым удобным образом; исходные файлы могут компилироваться отдельно и загружаться вместе наряду со скомпилированными ранее функциями из библиотек. Мы здесь не будем вдаваться в детали этого процесса, поскольку они зависят от используемой системы.
    Большинство программистов хорошо знакомы с “библиотечными”
    функциями для ввода и вывода /GETCHAR , PUTCHAR/ и для численных расчетов /SIN, COS, SQRT/. В этой главе мы сообщим больше о написании новых функций.
    4.1. Основные сведения.
    Для начала давайте разработаем и составим программу печати каждой строки ввода, которая содержит определенную комбинацию символов. /Это
    - специальный случай утилиты GREP системы “UNIX”/. Например, при поиске комбинации “THE” в наборе строк
    NOW IS THE TIME
    FOR ALL GOOD
    MEN TO COME TO THE AID
    OF THEIR PARTY
    в качестве выхода получим
    NOW IS THE TIME
    MEN TO COME TO THE AID
    OF THEIR PARTY
    основная схема выполнения задания четко разделяется на три части:
    WHILE (имеется еще строка)
    IF (строка содержит нужную комбинацию) вывод этой строки
    Конечно, возможно запрограммировать все действия в виде одной

    «Язык С» Б.В. Керниган, Д.М. Ричи
    73
    основной процедуры, но лучше использовать естественную структуру задачи и представить каждую часть в виде отдельной функции. С тремя маленькими кусками легче иметь дело, чем с одним большим, потому что отдельные не относящиеся к существу дела детали можно включить в функции и уменьшить возможность нежелательных взаимодействий. Кроме того, эти куски могут оказаться полезными сами по себе.
    “Пока имеется еще строка” - это GETLINE, функция, которую мы запрограммировали в главе 1, а “вывод этой строки” - это функция PRINTF,
    которую уже кто-то подготовил для нас. Это значит, что нам осталось только написать процедуру для определения, содержит ли строка данную комбинацию символов или нет. Мы можем решить эту проблему,
    позаимствовав разработку из PL/1: функция INDEX(S,т) возвращает позицию,
    или индекс, строки S, где начинается строка T, и -1, если S не содержит т . В
    качестве начальной позиции мы используем 0, а не 1, потому что в языке “C”
    массивы начинаются с позиции нуль. Когда нам в дальнейшем понадобится проверять на совпадение более сложные конструкции, нам придется заменить только функцию INDEX; остальная часть программы останется той же самой.
    После того, как мы потратили столько усилий на разработку, написание программы в деталях не представляет затруднений. ниже приводится целиком вся программа, так что вы можете видеть, как соединяются вместе отдельные части. Комбинация символов, по которой производится поиск, выступает пока в качестве символьной строки в аргументе функции INDEX, что не является самым общим механизмом. Мы скоро вернемся к обсуждению вопроса об инициализации символьных массивов и в главе 5 покажем, как сделать комбинацию символов параметром, которому присваивается значение в ходе выполнения программы. Программа также содержит новый вариант функции
    GETLINE; вам может оказаться полезным сравнить его с вариантом из главы 1.
    #DEFINE MAXLINE 1000
    MAIN() /* FIND ALL LINES MATCHING A PATTERN */
    CHAR LINE[MAXLINE];
    WHILE (GETLINE(LINE, MAXLINE) > 0)
    IF (INDEX(LINE, “THE”) >= 0)
    PRINTF(“%S”, LINE);
    GETLINE(S, LIM) /* GET LINE INTO S, RETURN LENGTH *
    CHAR S[];
    INT LIM;
    INT C, I;
    I = 0;
    WHILE(—LIM>0 && (C=GETCHAR()) != EOF && C != ‘\N’)

    74
    «Язык С» Б.В. Керниган, Д.М. Ричи
    S[I++] = C;
    IF (C == ‘\N’)
    S[I++] = C;
    S[I] = ‘\0’;
    RETURN(I);
    INDEX(S,T) /* RETURN INDEX OF T IN S,-1 IF NONE */
    CHAR S[], T[];
    INT I, J, K;
    FOR (I = 0; S[I] != ‘\0’; I++)
    FOR(J=I, K=0; T[K] !=’\0' && S[J] == T[K]; J++;
    K++)
    ;
    IF (T[K] == ‘\0’)
    RETURN(I);
    RETURN(-1);
    Каждая функция имеет вид, имя (список аргументов, если они имеются),
    описания аргументов, если они имеются, описания и операторы , если они имеются.
    Как и указывается, некоторые части могут отсутствовать; минимальной функцией является
    DUMMY ()
    которая не совершает никаких действий.
    /Такая ничего не делающая функция иногда оказывается удобной для сохранения места для дальнейшего развития программы/. если функция возвращает что-либо отличное от целого значения, то перед ее именем может стоять указатель типа; этот вопрос обсуждается в следующем разделе.
    Программой является просто набор определений отдельных функций.
    Связь между функциями осуществляется через аргументы и возвращаемые функциями значения /в этом случае/; ее можно также осуществлять через внешние переменные. Функции могут располагаться в исходном файле в любом порядке, а сама исходная программа может размещаться на нескольких файлах, но так, чтобы ни одна функция не расщеплялась.
    Оператор RETURN служит механизмом для возвращения значения из вызванной функции в функцию, которая к ней обратилась. За RETURN может следовать любое выражение:
    RETURN (выражение)
    Вызывающая функция может игнорировать возвращаемое значение, если

    «Язык С» Б.В. Керниган, Д.М. Ричи
    75
    она этого пожелает. Более того, после RETURN может не быть вообще никакого выражения; в этом случае в вызывающую программу не передается никакого значения. Управление также возвращется в вызывающую программу без передачи какого-либо значения и в том случае, когда при выполнении мы “проваливаемся” на конец функции, достигая закрывающейся правой фигурной скобки. EСли функция возвращает значение из одного места и не возвращает никакого значения из другого места, это не является незаконным,
    но может быть признаком каких-то неприятностей. В любом случае
    “значением” функции, которая не возвращает значения, несомненно будет мусор. Отладочная программа LINT проверяет такие ошибки.
    Механика компиляции и загрузки “C”-программ, расположенных в нескольких исходных файлах, меняется от системы к системе. В системе
    “UNIX”, например, эту работу выполняет команда ‘CC’, упомянутая в главе
    1. Предположим, что три функции находятся в трех различных файлах с именами MAIN.с, GETLINE.C и INDEX.с . Тогда команда
    CC MAIN.C GETLINE.C INDEX.C
    компилирует эти три файла, помещает полученный настраиваемый объектный код в файлы MAIN.O, GETLINE.O и INDEX.O и загружает их всех в выполняемый файл, называемый A.OUT .
    Если имеется какая-то ошибка, скажем в MAIN.C, то этот файл можно перекомпилировать отдельно и загрузить вместе с предыдущими объектными файлами по команде
    CC MAIN.C GETLIN.O INDEX.O
    Команда ‘CC’ использует соглашение о наименовании с “.с” и “.о” для того, чтобы отличить исходные файлы от объектных.
    Упражнение 4-1.
    Составьте программу для функции RINDEX(S,T), которая возвращает позицию самого правого вхождения т в S и -1, если S не содержит T.
    4.2. Функции, возвращающие нецелые значения.
    До сих пор ни одна из наших программ не содержала какого-либо описания типа функции. Дело в том, что по умолчанию функция неявно описывается своим появлением в выражении или операторе, как, например, в
    WHILE (GETLINE(LINE, MAXLINE) > 0)
    Если некоторое имя, которое не было описано ранее, появляется в выражении и за ним следует левая круглая скобка, то оно по контексту считается именем некоторой функции. Кроме того, по умолчанию предполагается, что эта функция возвращает значение типа INT. Так как в выражениях CHAR преобразуется в INT, то нет необходимости описывать

    76
    «Язык С» Б.В. Керниган, Д.М. Ричи
    функции, возвращающие CHAR. Эти предположения покрывают большинство случаев, включая все приведенные до сих пор примеры.
    Но что происходит, если функция должна возвратить значение какого-то другого типа ? Многие численные функции, такие как SQRT, SIN и COS
    возвращают DOUBLE; другие специальные функции возвращают значения других типов. Чтобы показать, как поступать в этом случае, давайте напишем и используем функцию ATоF(S), которая преобразует строку S в эквивалент- ное ей плавающее число двойной точности. Функция ATоF является расширением атоI, варианты которой мы написали в главах 2 и 3; она обрабатывает необязательно знак и десятичную точку, а также целую и дробную часть, каждая из которых может как присутствовать, так и отсутствовать./эта процедура преобразования ввода не очень высокого качества; иначе она бы заняла больше места, чем нам хотелось бы/.
    Во-первых, сама ATоF должна описывать тип возвращаемого ею значения,
    поскольку он отличен от INT. Так как в выражениях тип FLOAT преобразуется в DOUBLE, то нет никакого смысла в том, чтобы ATOF возвращала FLOAT;
    мы можем с равным успехом воспользоваться дополнительной точностью,
    так что мы полагаем, что возвращаемое значение типа DOUBLE. Имя типа должно стоять перед именем функции, как показывается ниже:
    DOUBLE ATOF(S) /* CONVERT STRING S TO DOUBLE */
    CHAR S[];
    DOUBLE VAL, POWER;
    INT I, SIGN;
    FOR(I=0; S[I]==’ ‘ \!\! S[I]==’\N’ \!\! S[I]==’\T’;
    I++)
    ;
    /* SKIP WHITE SPACE */
    SIGN = 1;
    IF (S[I] == ‘+’ \!\! S[I] == ‘-’) /* SIGN */
    SIGN = (S[I++] == ‘+’) ? 1 : -1;
    FOR (VAL = 0; S[I] >= ‘0’ && S[I] <= ‘9’; I++)
    VAL = 10 * VAL + S[I] - ‘0’;
    IF (S[I] == ‘.’)
    I++;
    FOR (POWER = 1; S[I] >= ‘0’ && S[I] <= ‘9’; I++)
    VAL = 10 * VAL + S[I] - ‘0’;
    POWER *= 10;
    RETURN(SIGN * VAL / POWER);
    Вторым, но столь же важным, является то, что вызывающая функция должна объявить о том, что ATOF возвращает значение, отличное от INT типа. Такое объявление демонстрируется на примере следующего примитивного

    «Язык С» Б.В. Керниган, Д.М. Ричи
    77
    настольного калькулятора /едва пригодного для подведения баланса в чековой книжке/, который считывает по одному числу на строку, причем это число может иметь знак, и складывает все числа, печатая сумму после каждого ввода.
    #DEFINE MAXLINE 100
    MAIN() /* RUDIMENTARY DESK CALKULATOR */
    DOUBLE SUM, ATOF();
    CHAR LINE[MAXLINE];
    SUM = 0;
    WHILE (GETLINE(LINE, MAXLINE) > 0)
    PRINTF(“\T%.2F\N”,SUM+=ATOF(LINE));
    Оисание
    DOUBLE SUM, ATOF();
    говорит, что SUM является переменной типа DOUBLE , и что ATOF
    является функцией, возвращающей значение типа DOUBLE .
    Эта мнемоника означает, что значениями как SUM, так и ATOF(...)
    являются плавающие числа двойной точности.
    Если функция ATOF не будет описана явно в обоих местах, то в “C”
    предполагается, что она возвращает целое значение, и вы получите бессмысленный ответ. Если сама ATOF и обращение к ней в MAIN имеют несовместимые типы и находятся в одном и том же файле, то это будет обнаружено компилятором. Но если ATOF была скомпилирована отдельно /
    что более вероятно/, то это несоответствие не будет зафиксировано, так что
    ATOF будет возвращать значения типа DOUBLE, с которым MAIN будет обращаться, как с INT , что приведет к бессмысленным результатам. /
    Программа LINT вылавливает эту ошибку/.
    Имея ATOF, мы, в принципе, могли бы с ее помощью написать ATOI
    (преобразование строки в INT):
    ATOI(S) /* CONVERT STRING S TO INTEGER */
    CHAR S[];
    DOUBLE ATOF();
    RETURN(ATOF(S));
    Обратите внимание на структуру описаний и оператор RETURN.
    Значение выражения в
    RETURN (выражение)
    всегда преобразуется к типу функции перед выполнением самого возвращения. Поэтому при появлении в операторе RETURN значение

    78
    «Язык С» Б.В. Керниган, Д.М. Ричи
    функции атоF, имеющее тип DOUBLE, автоматически преобразуется в INT,
    поскольку функция ATOI возвращает INT. (Как обсуждалось в главе 2,
    преобразование значения с плавающей точкой к типу INT осуществляется посредством отбрасывания дробной части).
    1   ...   4   5   6   7   8   9   10   11   ...   23


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