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

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

  • Основные сведения о функциях 97

  • 1116 98 _ Глава 4. Функции и структура программы 4.2. Функции, возвращающие нецелые значения

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

  • 4.3. Внешние переменные

  • 4.3. Внешние переменные _ юз

  • 4.3. Внешние переменные _ 105

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

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


    Скачать 31.48 Mb.
    НазваниеБ. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003
    АнкорБ. Керриган, Д. Ритчи Язык программирования C.pdf
    Дата06.04.2017
    Размер31.48 Mb.
    Формат файлаpdf
    Имя файлаБ. Керриган, Д. Ритчи Язык программирования C.pdf
    ТипКнига
    #4546
    страница9 из 28
    1   ...   5   6   7   8   9   10   11   12   ...   28
    Основные сведения о функциях ___ 95
    Чтобы решить эту задачу, мы напишем функцию кото- рая указывает место (индекс) в строке s, где начинается строка t, или если s не содержит Так как в Си нумерация элементов в массивах начи- нается с нуля, отрицательное число -1 подходит в качестве признака не- удачного поиска. Если далее нам потребуется более сложное отождеств- ление по образцу, мы просто заменим st ri ndex на другую функцию, оста- вив при этом остальную часть программы без изменений.
    функция st rst г аналогична функции st rindex и отличается от последней только что возвращает не индекс, а указатель.)
    После такого проектирования ее "деталировка" оказывает- ся очевидной. Мы имеем представление о программе в целом и знаем, как взаимодействуют ее части. В нашей программе образец для поиска зада- ется строкой-литералом, что снижает ее универсальность. В главе 5 мы еще вернемся к проблеме инициализации символьных массивов и пока- жем, как образец сделать параметром, устанавливаемым при запуске про- граммы. Здесь приведена несколько измененная версия функции getline,
    и было бы поучительно сравнить ее с версией, рассмотренной в главе 1.
    1000 /* максимальный размер вводимой строки int getline(char line[], int max);
    int char char pattern[] =
    /* образец для поиска */
    /* найти все строки, содержащие образец */
    {
    char int found = 0;
    while
    MAXLINE) > 0)
    if (strindex(line, pattern) >= 0) {
    printf line);
    found++;
    }
    return found;
    /* getline: читает строку в s, возвращает длину */
    int getline(char int
    {
    int

    96 _ Глава 4. Функции и структура программы
    i = 0;
    while
    > 0 &&
    != EOF && с !=
    s[i++] = с;
    if (с ==
    s [i++] = c;
    s[i]
    =
    return
    /*
    вычисляет место t в s или выдает -1, если t нет в s */
    int strindex (char s[], char
    {
    int i, j, k;
    for (i
    = 0; s[i] !=
    i++)
    {
    for (j = i, k = 0; t[k] !=
    && s[j] ==
    j++, k++)
    if (k > 0 && t[k] ==
    return i;
    }
    return
    Определение любой функции имеет следующий вид:
    имя-функции
    аргументов)
    {
    объявления и инструкции
    }
    Отдельные части определения могут отсутствовать,
    например, в опре- делении "минимальной" функции
    {}
    которая ничего не вычисляет и ничего не возвращает. Такая ничего не де- лающая функция в процессе разработки программы бывает полезна в ка- честве "хранителя места". Если тип результата опущен, то предполага- ется, что функция возвращает значение типа int.
    Любая программа - это просто совокупность определений переменных и функций. Связи между функциями осуществляются через аргументы,
    возвращаемые значения и внешние переменные. В исходном файле функ- ции могут располагаться в любом порядке; исходную программу можно разбивать на любое число файлов, но так, чтобы ни одна из функций не оказалась разрезанной.

    Основные сведения о функциях 97
    Инструкция return реализует механизм возврата результата от вызы- ваемой функции к вызывающей. За словом может следовать лю- бое выражение:
    return
    Если потребуется, выражение будет приведено к возвращаемому типу функции. Часто выражение заключают в скобки, но они не обязательны.
    Вызывающая функция вправе проигнорировать возвращаемое значе- ние. Более того, выражение в retu rn может отсутствовать, и тогда вообще никакое значение не будет возвращено в вызывающую функцию. Управ- ление возвращается в вызывающую функцию без результирующего зна- чения также и в том случае, когда вычисления достигли "конца" (т. е. по- следней закрывающей фигурной скобки функции). Не запрещена (но долж- на вызывать настороженность) ситуация, когда в одной и той же функ- ции одни rn имеют при себе выражения, а другие - не имеют. Во всех случаях, когда функция "забыла" передать результат в retu rn, она обяза- тельно выдаст "мусор".
    Функция main в программе поиска по образцу возвращает в качестве результата количество найденных строк. Это число доступно той среде,
    из которой данная программа была вызвана.
    Механизмы компиляции и загрузки расположенных в нескольких исходных файлах, в разных системах могут различаться.
    В системе UNIX, например, эти работы выполняет упомянутая в главе 1
    команда
    Предположим, что три функции нашего последнего примера расположены в трех разных файлах:
    с,
    с. Тогда команда скомпилирует указанные файлы, поместив результат компиляции в фай- лы модулей о, get о и о, и затем загрузит их в исполняемый файл out. Если обнаружилась ошибка, например в файле с, то его можно скомпилировать снова и результат загру- зить ранее полученными объектными файлами, выполнив следующую команду:
    Команда использует стандартные расширения файлов с"
    о", что- бы отличать исходные файлы от объектных.
    Упражнение 4.1. Напишите функцию s t r i n d e x ( s , t), которая выдает позицию самого правого вхождения t в s или -1, если вхождения не об- наружено.
    1116

    98 _ Глава 4. Функции и структура программы
    4.2. Функции, возвращающие
    нецелые значения
    В предыдущих примерах функции либо вообще не возвращали резуль- тирующих значений (void), либо возвращали значения типа int. А как быть, когда результат функции должен иметь другой тип? Многие вы- числительные функции, как, например, sq rt, sin и cos, возвращают значе- ния типа double; другие специальные функции могут выдавать значения еще каких-то типов. Чтобы проиллюстрировать, каким образом функция может возвратить нецелое значение, напишем функцию atof которая переводит строку s в соответствующее число с плавающей точкой двой- ной точности. Функция atof представляет собой расширение функции atoi, две версии которой были рассмотрены в главах 2 и 3. Она имеет де- ло со знаком (которого может и не быть), с десятичной точкой, а также с целой и дробной частями, одна из которых может отсутствовать. Наша версия не является высококачественной программой преобразования вво- димых чисел; такая программа потребовала бы заметно больше памяти.
    Функция atof входит в стандартную библиотеку программ; ее описание содержится в заголовочном файле
    Прежде всего отметим, что объявлять тип возвращаемого значения должна сама так как этот тип не есть int. Указатель типа задается перед именем функции.
    /*
    преобразование строки s double */
    double atof (char
    {
    double power;
    int i, sign;
    (i 0; isspace i++)
    ;
    игнорирование левых символов-разделителей */
    sign = (s[i] ==
    ? -1 : 1;
    if
    ==
    for (val = 0.0; isdigit i
    val = 10.0 * val + (s[i] - if for (power = 1.0;
    i++
    val
    = 10.0 * val + (s[i] -

    4.2. Функции, возвращающие нецелые значения 99
    power *=
    ,
    f
    - return sign *
    / power;
    Кроме того, важно, чтобы вызывающая программа знала, что atof возвращает нецелое значение. Один из способов обеспечить это - явно описать atof в вызывающей программе. Подобное описание демонстри- руется ниже в программе простенького калькулятора (достаточного для проверки баланса чековой книжки), который каждую вводимую строку воспринимает как число, прибавляет его к текущей сумме и печатает ее новое значение.

    MAXLINE 100
    /* примитивный калькулятор */
    double sum, atof char int getline (char line[], int max);
    sum
    = 0;
    while
    MAXLINE) > 0)
    sum +=
    return 0;
    В объявлении
    - double sum, atof говорится, что sum - переменная типа double, a atof - функция, которая принимает один аргумент типа
    ] и возвращает результат типа double.
    Объявление и определение функции atof должны соответствовать друг другу. Если в одном исходном файле сама функция atof и обращение к ней в main имеют разные типы, то это несоответствие будет зафиксиро- вано компилятором как ошибка. Но если функция atof была скомпили- рована отдельно (что более вероятно), то несоответствие типов не будет обнаружено, и atof возвратит значение типа double, которое функция main воспримет как int, что приведет к бессмысленному результату.
    Это последнее утверждение, вероятно, вызовет у вас удивление, по- скольку ранее говорилось о необходимости соответствия объявлений

    Глава 4. Функции и структура программы
    и определений. Причина несоответствия, возможно, будет следствием того, что вообще отсутствует прототип функции, и функция неявно объяв- ляется при первом своем появлении в выражении, как, например, в sum += atof(line)
    Если в выражении встретилось имя, нигде ранее не объявленное, за кото- рым следует открывающая скобка, то такое имя по контексту считается именем функции, возвращающей результат типа при этом относи- тельно ее аргументов ничего не предполагается. Если в объявлении функ- ции аргументы не указаны, как в double то и в этом случае что ничего об аргументах atof не известно,
    и все проверки на соответствие ее параметров будут выключены. Предпо- лагается, что такая специальная интерпретация пустого списка позволит новым компиляторам транслировать старые Си-программы. Но в новых программах пользоваться этим - не очень хорошая идея. Если у функции есть аргументы, опишите их, если их нет, используйте слово void.
    Располагая соответствующим образом описанной функцией мы можем написать функцию atoi, преобразующую строку символов в целое значение, следующим образом:
    /*
    преобразование строки s в int с помощью atof */
    int atoi
    {
    double atof (char return (int) atof (s);
    }
    Обратите внимание на вид объявления и инструкции return. Значение выражения в
    выражение;
    перед тем, как оно будет возвращено в качестве результата, приводится к типу функции. Следовательно, поскольку функция atoi возвращает зна- чение int, результат вычисления atof типа double в инструкции r e t u r n автоматически преобразуется в тип int. При преобразовании возможна потеря информации, и некоторые компиляторы предупреждают об этом.
    Оператор приведения явно указывает на необходимость преобразования типа и подавляет любое предупреждающее сообщение.
    Упражнение 4.2. Дополните функцию atof таким чтобы она справлялась с числами вида

    4.3. Внешние переменные 101
    в которых после мантиссы может стоять е (или Е) с последующим поряд- ком (быть может, со
    4.3. Внешние переменные
    Программа на Си обычно оперирует с множеством внешних объектов:
    переменных и функций. Прилагательное "внешний"
    противо- положно прилагательному "внутренний", которое относится к аргумен- там и переменным, определяемым внутри функций. Внешние перемен- ные определяются вне функций и потенциально доступны для многих функций. Сами функции всегда являются внешними объектами, посколь- ку в Си запрещено определять функции внутри других функций. По умол- чанию одинаковые внешние имена, используемые в разных от- носятся к одному и тому же внешнему объекту (функции). (В стандарте это
    редактированием внешних связей (external
    В этом смысле внешние переменные похожи на области COMMON в фортране и на переменные самого внешнего блока в Паскале. Позже мы покажем, как внешние функции и переменные сделать видимыми только внутри одно- го исходного файла.
    Поскольку внешние переменные доступны всюду, их можно использо- вать в качестве связующих данных между функциями как альтернативу связей через аргументы и возвращаемые значения. Для любой функции внешняя переменная доступна по ее имени, если это имя было должным образом объявлено.
    Если число переменных, совместно используемых функциями, вели- ко, связи между последними через внешние переменные могут оказаться более удобными и эффективными, чем длинные списки аргументов.
    как отмечалось в главе 1, к этому заявлению следует относиться крити- чески, поскольку такая практика ухудшает структуру программы и при- водит к слишком большому числу связей между функциями по данным.
    Внешние переменные полезны, так как они имеют большую область действия и время жизни. Автоматические переменные существуют толь- ко внутри функции, они возникают в момент входа в функцию и исчеза- ют при выходе из нее. Внешние переменные, напротив, существуют по- стоянно, так что их значения сохраняются и между обращениями к функ- циям. Таким образом, если двум функциям приходится пользоваться од- ними и теми же данными и ни одна из них не вызывает другую, то часто '
    ужо и русский слово
    Примеч. ред.

    1.02 Глава 4. Функции и структура программы бывает удобно оформить эти общие данные в виде внешних переменных,
    а не передавать их в функцию и обратно через аргументы.
    В связи с приведенными рассуждениями разберем пример. Поставим себе задачу написать программу-калькулятор, понимающую операторы
    +, -, * и /. Такой калькулятор легче будет написать, если ориентироваться на польскую, а не инфиксную запись выражений. (Обратная польская запись применяется в некоторых карманных калькуляторах и в таких языках, как Forth и Postscript.)
    В обратной польской записи каждый оператор следует за своими опе- рандами. Выражение в инфиксной записи, скажем
    (1 - 2) * (4 + 5)
    в польской записи представляется как
    Скобки не нужны, неоднозначности в вычислениях не поскольку известно, сколько операндов требуется для каждого оператора.
    Реализовать нашу программу весьма просто. Каждый операнд посыла- ется в стек; если встречается оператор, то из стека берется соответству- ющее число операндов (в случае бинарных операторов два) и выполняет- ся операция, после чего результат посылается в стек. В нашем примере числа 1 и 2 посылаются в стек, затем замещаются на их разность
    Далее в стек посылаются числа 4 и 5, которые затем заменяются их суммой (9).
    Числа -1 и 9 заменяются в стеке их произведением (т. е. -9). Встретив сим- вол новой строки, программа извлекает значение из стека и печатает его.
    Таким образом, программа состоит из цикла, обрабатывающего на каж- дом своем шаге очередной встречаемый оператор или операнд:
    while
    элемент не
    if (число)
    его в стек
    else if (оператор)
    из стека операнды
    операцию
    результат
    в стек
    else if
    взять с вершины стека число и
    else
    ошибка
    Операции "послать в стек" и "взять из стека" сами по себе тривиальны,
    однако по мере добавления к ним механизмов обнаружения и нейтра- лизации ошибок становятся достаточно длинными. Поэтому их лучше

    4.3. Внешние переменные _ юз
    оформить в виде отдельных функций, чем повторять соответствующий код по всей программе. И конечно необходимо иметь отдельную функ- цию для получения очередного оператора или операнда.
    Главный вопрос, который мы еще не рассмотрели, - это вопрос о том,
    где расположить стек и каким функциям разрешить к нему прямой доступ. Стек можно расположить в функции main и передавать сам стек и текущую позицию в нем в качестве аргументов функциям push ("по- слать в стек") и pop ("взять из стека"). Но функции main нет дела до пере- менных, относящихся к стеку, - ей нужны только операции по помеще- нию чисел в стек и извлечению их оттуда. Поэтому мы решили стек и свя- занную с ним информацию хранить во внешних переменных, доступных для функций push и pop, но не доступных для
    Переход от эскиза к программе достаточно легок. Если теперь му представить как текст, расположенный в одном исходном файле, она будет иметь следующий вид:
    /* могут быть в любом количестве */
    /* могут быть любом количестве */
    объявления функций для main
    внешние переменные для push и pop void push (double f)
    double pop (void)
    int getop(char
    подпрограммы, вызываемые функцией getop
    Позже мы как текст этой программы можно разбить на два или большее число файлов.
    Функция main - это цикл, содержащий большой переключатель switch,
    передающий управление на ту или иную ветвь в зависимости от типа опе- ратора или операнда. Здесь представлен более типичный случай примене- ния переключателя switch по сравнению с рассмотренным в параграфе 3.4.
    /* для atof() */
    MAXOP 100 /*
    размер операнда
    */
    NUMBER
    /* признак числа */

    104 Глава 4. Функции и структура программы int getop (char []);
    void push (double);
    double pop (void);
    /* калькулятор с обратной польской записью */
    main ()
    {
    int type;
    double op2;
    char while
    = getop (s)) != EOF) {
    switch (type) {
    case NUMBER:
    push (atof break;
    case push (pop() +
    break;
    case push
    () * pop ());
    break;
    case op2 = pop ();
    push (pop () - op2);
    break;
    case
    :
    op2 = pop ();
    if (op2 != 0.0)
    push (pop () / op2);
    else деление на break;
    case
    :
    ());
    break;
    неизвестная операция break;
    return 0;
    }

    4.3. Внешние переменные _ 105
    Так как операторы + и * коммутативны, порядок, в котором операнды бе- рутся из стека, не важен, однако в случае операторов - и /, левый и пра- вый операнды должны различаться. Так, в
    -
    /* НЕПРАВИЛЬНО */
    очередность обращения к pop не определена. Чтобы гарантировать пра- вильную очередность, необходимо первое значение из стека присвоить временной переменной, как это и сделано в main.
    MAXVAL 100 /* максимальная глубина стека */
    int sp = 0; /* следующая свободная позиция стеке */
    double
    /* стек */
    /* push: положить значение f стек */
    void f)
    {
    if (sp < MAXVAL)
    = f;
    else printf( "ошибка: стек полон, %g не f);
    /* pop: взять с вершины стека и выдать в качестве результата */
    double pop(void)
    {
    if
    (sp
    > 0)
    return else {
    стек return 0.0;
    Переменная считается внешней, если она определена вне функции. Та- ким образом, стек и индекс стека, которые должны быть доступны и для push, и для pop, определяются вне этих функций. Но main не использует ни стек, ни позицию в стеке, и поэтому их представление может быть скры- то от main.
    Займемся реализацией getop - функции, получающей следующий опе- ратор или операнд. Нам предстоит решить довольно простую задачу. Более точно: требуется пропустить пробелы и табуляции; если следующий сим- вол - не цифра и не десятичная точка, то нужно выдать его; в противном

    _ Глава 4. Функции и структура программы
    случае надо накопить строку цифр с десятичной точкой, если она и выдать число NUMBER в качестве результата.

    int void
    /* getop: получает следующий оператор или операнд */
    int int i, c;
    while
    = с =
    с ==
    s[1]
    if
    && с return с; /* не число */
    i = 0;
    if
    /* накапливаем целую часть */
    while (isdigit(s[++i] = с =
    if (с
    /* накапливаем дробную часть */
    while
    = с =
    =
    if (c !=
    EOF)
    return NUMBER;
    Как работают функции get с h и ungetch? Во многих случаях программа не может "сообразить", прочла ли она все, что требуется, пока не прочтет лишнего. Так, накопление числа производится до тех пор, пока не встре- тится символ, отличный от цифры. Но это означает, что программа про- чла на один символ больше, чем нужно, и последний символ нельзя чать в число.
    Эту проблему можно было бы решить при наличии обратной чтению операции с помощью которой можно было бы вернуть ненужный символ. Тогда каждый раз, когда программа считает на один символ больше, чем требуется, эта операция возвращала бы его вводу,
    и остальная часть программы могла бы вести себя так, будто этот символ

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


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