Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

ЯЗЫК С

     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 (строка содержит нужную комбинацию)
          вывод этой строки
           

                           - 74 -
 
     Конечно, возможно запрограммировать все действия в виде
 одной основной процедуры, но лучше использовать естественную
 структуру задачи и представить каждую часть в виде отдельной
 функции. С тремя маленькими кусками легче иметь дело, чем с
 одним большим, потому что отдельные не относящиеся к сущест-
 ву дела детали можно включить в функции и уменьшить возмож-
 ность нежелательных взаимодействий. Кроме того, эти куски
 могут оказаться полезными сами по себе.
 
     "Пока имеется еще строка" - это 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);
  }
      
                           - 75 -
     
     
 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')
  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 ()  { }
 
 которая не совершает никаких действий.
 
       /Такая ничего не делающая функция иногда оказывается
 удобной для сохранения места для дальнейшего развития прог-
 раммы/. если функция возвращает что-либо отличное от целого
 значения, то перед ее именем может стоять указатель типа;
 этот вопрос обсуждается в следующем разделе.
     
                           - 76 -
     
       Программой является просто набор определений отдельных
 функций. Связь между функциями осуществляется через аргумен-
 ты и возвращаемые функциями значения /в этом случае/; ее
 можно также осуществлять через внешние переменные. Функции
 могут располагаться в исходном файле в любом порядке, а сама
 исходная программа может размещаться на нескольких файлах,
 но так, чтобы ни одна функция не расщеплялась.
       Оператор RETURN служит механизмом для возвращения зна-
 чения из вызванной функции в функцию, которая к ней обрати-
 лась. За RETURN может следовать любое выражение:
 
    RETURN (выражение)
 
       Вызывающая функция может игнорировать возвращаемое
 значение, если она этого пожелает. Более того, после 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.
     
                           - 77 -
 
      4.2. Функции, возвращающие нецелые значения.
 
     До сих пор ни одна из наших программ не содержала како-
 го-либо описания типа функции. Дело в том, что по умолчанию
 функция неявно описывается своим появлением в выражении или
 операторе, как, например, в
 
  WHILE (GETLINE(LINE, MAXLINE) > 0)
 
     Если некоторое имя, которое не было описано ранее, появ-
 ляется в выражении и за ним следует левая круглая скобка, то
 оно по контексту считается именем некоторой функции. Кроме
 того, по умолчанию предполагается, что эта функция возвраща-
 ет значение типа INT. Так как в выражениях CHAR преобразует-
 ся в INT, то нет необходимости описывать функции, возвращаю-
 щие 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;
           
                           - 78 -
     
 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] = '0' && S[I]  0)
    PRINTF("\T%.2F\N",SUM+=ATOF(LINE));
 
 
  Оисание
 
      DOUBLE  SUM, ATOF();
 
 
 говорит, что SUM является переменной типа DOUBLE , и что
 ATOF является функцией, возвращающей значение типа DOUBLE .
 Эта мнемоника означает, что значениями как SUM, так и
 ATOF(...) являются плавающие числа двойной точности.
     
                           - 79 -
     
     Если функция 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 значе-
 ние функции атоF, имеющее тип DOUBLE, автоматически преобра-
 зуется в INT, поскольку функция ATOI возвращает INT. (Как
 обсуждалось в главе 2, преобразование значения с плавающей
 точкой к типу INT осуществляется посредством отбрасывания
 дробной части).
     Упражнение  4-2.
     ----------------
     Расширьте ATOF таким образом, чтобы она могла работать с
 числами вида
 
     123.45е-6
 
 где за числом с плавающей точкой может следовать 'E' и пока-
 затель экспоненты, возможно со знаком.
 
     4.3. Еще об аргументах функций.
 
     В главе 1 мы уже обсуждали тот факт , что аргументы фун-
 кций передаются по значению, т.е. вызванная функция получает
 свою временную копию каждого аргумента, а не его адрес. это
 означает, что вызванная функция не может воздействовать на
 исходный аргумент в вызывающей функции. Внутри функции каж-
 дый аргумент по существу является локальной переменной, ко-
 торая инициализируется тем значением, с которым к этой функ-
 ции обратились.
     
                           - 80 -
     
     Если в качестве аргумента функции выступает имя массива,
 то передается адрес начала этого массива; сами элементы не
 копируются. Функция может изменять элементы массива, исполь-
 зуя индексацию и адрес начала. Таким образом, массив переда-
 ется по ссылке. В главе 5 мы обсудим, как использование ука-
 зателей позволяет функциям воздействовать на отличные от
 массивов переменные в вызывающих функциях.
     Между прочим, несуществует полностью удовлетворительного
 способа написания переносимой функции с переменным числом
 аргументов. Дело в том, что нет переносимого способа, с по-
 мощью которого вызванная функция могла бы определить, сколь-
 ко аргументов было фактически передано ей в данном обраще-
 нии. Таким образом, вы, например, не можете написать дейст-
 вительно переносимую функцию, которая будет вычислять макси-
 мум от произвольного числа аргументов, как делают встроенные
 функции MAX в фортране и PL/1.
     Обычно со случаем переменного числа аргументов безопасно
 иметь дело, если вызванная функция не использует аргументов,
 которые ей на самом деле не были переданы, и если типы сог-
 ласуются. Самая распространенная в языке "C" функция с пере-
 менным числом - PRINTF . Она получает из первого аргумента
 информацию, позволяющую определить количество остальных ар-
 гументов и их типы. Функция PRINTF работает совершенно неп-
 равильно, если вызывающая функция передает ей недостаточное
 количество аргументов, или если их типы не согласуются с ти-
 пами, указанными в первом аргументе. Эта функция не является
 переносимой и должна модифицироваться при использовании в
 различных условиях.
     Если же типы аргументов известны, то конец списка аргу-
 ментов можно отметить, используя какое-то соглашение; напри-
 мер, считая, что некоторое специальное значение аргумента
 (часто нуль) является признаком конца аргументов.
 
      4.4. Внешние переменные.
 
     Программа на языке "C" состоит из набора внешних объек-
 тов, которые являются либо переменными, либо функциями. Тер-
 мин "внешний" используется главным образом в противопостав-
 ление термину "внутренний", которым описываются аргументы и
 автоматические переменные, определенные внурти функций.
 Внешние переменные определены вне какой-либо функции и, та-
 ким образом, потенциально доступны для многих функций. Сами
 функции всегда являются внешними, потому что правила языка
 "C" не разрешают определять одни функции внутри других. По
 умолчанию внешние переменные являются также и "глобальными",
 так что все ссылки на такую переменную, использующие одно и
 то же имя (даже из функций, скомпилированных независимо),
 будут ссылками на одно и то же. В этом смысле внешние пере-
 менные аналогичны переменным COмMON в фортране и EXTERNAL в
 PL/1. Позднее мы покажем, как определить внешние переменные
 и функции таким образом, чтобы они были доступны не глобаль-
 но, а только в пределах одного исходного файла.
     
                           - 81 -
     
     В силу своей глобальной доступности внешние переменные
 предоставляют другую, отличную от аргументов и возвращаемых
 значений, возможность для обмена данными между функциями.
 Если имя внешней переменной каким-либо образом описано, то
 любая функция имеет доступ к этой переменной, ссылаясь к ней
 по этому имени.
     В случаях, когда связь между функциями осуществляется с
 помощью большого числа переменных, внешние переменные оказы-
 ваются более удобными и эффективными, чем использование
 длинных списков аргументов. Как, однако, отмечалось в главе
 1, это соображение следует использовать с определенной осто-
 рожностью, так как оно может плохо отразиться на структуре
 программ и приводить к программам с большим числом связей по
 данным между функциями.
     Вторая причина использования внешних переменных связана
 с инициализацией. В частности, внешние массивы могут быть
 инициализированы а автоматические нет. Мы рассмотрим вопрос
 об инициализации в конце этой главы.
     Третья причина использования внешних переменных обуслов-
 лена их областью действия и временем существования. Автома-
 тические переменные являются внутренними по отношению к фун-
 кциям; они возникают при входе в функцию и исчезают при вы-
 ходе из нее. Внешние переменные, напротив, существуют посто-
 янно. Они не появляютя и не исчезают, так что могут сохра-
 нять свои значения в период от одного обращения к функции до
 другого. В силу этого, если две функции используют некоторые
 общие данные, причем ни одна из них не обращается к другой ,
 то часто наиболее удобным оказывается хранить эти общие дан-
 ные в виде внешних переменных, а не передавать их в функцию
 и обратно с помощью аргументов.
     Давайте продолжим обсуждение этого вопроса на большом
 примере. Задача будет состоять в написании другой программы
 для калькулятора, лучшей,чем предыдущая. Здесь допускаются
 операции +,-,*,/ и знак = (для выдачи ответа).вместо инфикс-
 ного представления калькулятор будет использовать обратную
 польскую нотацию,поскольку ее несколько легче реализовать.в
 обратной польской нотации знак следует за операндами; инфик-
 сное выражение типа
 
    (1-2)*(4+5)=
     
   записывается в виде
    12-45+*=
   круглые скобки при этом не нужны
           
                           - 82 -
     
     Реализация оказывается весьма простой.каждый операнд по-
 мещается в стек; когда поступает знак операции,нужное число
 операндов (два для бинарных операций) вынимается,к ним при-
 меняется операция и результат направляется обратно в
 стек.так в приведенном выше примере 1 и 2 помещаются в стек
 и затем заменяются их разностью, -1.после этого 4 и 5 вво-
 дятся в стек и затем заменяются своей суммой,9.далее числа
 -1 и 9 заменяются в стеке на их произведение,равное -9.опе-
 рация = печатает верхний элемент стека, не удаляя его (так
 что промежуточные вычисления могут быть проверены).
     Сами операции помещения чисел в стек и их извлечения
 очень просты,но, в связи с включением в настоящую программу
 обнаружения ошибок и восстановления,они оказываются доста-
 точно длинными. Поэтому лучше оформить их в виде отдельных
 функций,чем повторять соответствующий текст повсюду в прог-
 рамме. Кроме того, нужна отдельная функция для выборки из
 ввода следующей операции или операнда. Таким образом, струк-
 тура программы имеет вид:
 
 WHILE( поступает операция или операнд, а не конец
    IF ( число )
         поместить его в стек
    еLSE IF ( операция )
         вынуть операнды из стека
         выполнить операцию
         поместить результат в стек
    ELSE
         ошибка
 
     Основной вопрос, который еще не был обсужден, заключает-
 ся в том,где поместить стек, т. Е. Какие процедуры смогут
 обращаться к нему непосредственно. Одна из таких возможнос-
 тей состоит в помещении стека в MAIN и передачи самого стека
 и текущей позиции в стеке функциям, работающим со стеком. Но
 функции MAIN нет необходимости иметь дело с переменными, уп-
 равляющими стеком; ей естественно рассуждать в терминах по-
 мещения чисел в стек и извлечения их оттуда. В силу этого мы
 решили сделать стек и связанную с ним информацию внешними
 переменными , доступными функциям PUSH (помещение в стек) и
 POP (извлечение из стека), но не MAIN.
     Перевод этой схемы в программу достаточно прост. Ведущая
 программа является по существу большим переключателем по ти-
 пу операции или операнду; это, по-видимому, более характер-
 ное применеие переключателя, чем то, которое было продемонс-
 трировано в главе 3.
     
    #DEFINE MAXOP   20  /* MAX SIZE OF OPERAND, OPERАTOR *
    #DEFINE NUMBER '0'  /* SIGNAL THAT NUMBER FOUND */
    #DEFINE TOOBIG '9'  /* SIGNAL THAT STRING IS TOO BIG *
           
                           - 83 -
     
    MAIN()  /* REVERSE POLISH DESK CALCULATOR */
    /(
     INT TUPE;
     CHAR S[MAXOP];
     DOUBLE OP2,ATOF(),POP(),PUSH();
 
     WHILE ((TUPE=GETOP(S,MAXOP)) !=EOF);
       SWITCH(TUPE) /(
       CASE NUMBER:
            PUSH(ATOF(S));
            BREAK;
       CASE '+':
            PUSH(POP()+POP());
            BREAK;
       CASE '*':
            PUSH(POP()*POP());
            BREAK;
       CASE '-':
            OP2=POP();
            PUSH(POP()-OP2);
            BREAK;
       CASE '/':
            OP2=POP();
            IF (OP2 != 0.0)
    PUSH(POP()/OP2);
            ELSE
               PRINTF("ZERO DIVISOR POPPED\N");
            BREAK;
       CASE '=':
            PRINTF("\T%F\N",PUSH(POP()));
            BREAK;
       CASE 'C':
            CLEAR();
            BREAK;
       CASE TOOBIG:
            PRINTF("%.20S ... IS TOO LONG\N",S)
            BREAK;
       /)
    /)
    #DEFINE MAXVAL 100 /* MAXIMUM DEPTH OF VAL STACK */
           
                           - 84 -
     
    INT SP = 0;        /* STACK POINTER */
    DOUBLE VAL[MAXVAL]; /*VALUE STACK */
    DOUBLE PUSH(F)    /* PUSH F ONTO VALUE STACK */
    DOUBLE F;
    /(
     IF (SP  0)
             RETURN(VAL[--SP]);
     ELSE    /(
             PRINTF("ERROR: STACK EMPTY\N");
             CLEAR();
             RETURN(0);
     /)
    /)
 
    CLEAR()       /* CLEAR STACK */
    /(
      SP=0;
    /)
 
     Команда C очищает стек с помощью функции CLEAR, которая
 также используется в случае ошибки функциями PUSH и POP. к
 функции GETOP мы очень скоро вернемся.
     Как уже говорилось в главе 1, переменная является внеш-
 ней, если она определена вне тела какой бы то ни было функ-
 ции. Поэтому стек и указатель стека, которые должны исполь-
 зоваться функциями PUSH, POP и CLEAR, определены вне этих
 трех функций. Но сама функция MAIN не ссылается ни к стеку,
 ни к указателю стека - их участие тщательно замаскировано. В
 силу этого часть программы, соответствующая операции = , ис-
 пользует конструкцию
  
    PUSH(POP());
 
 
 для того, чтобы проанализировать верхний элемент стека, не
 изменяя его.
     Отметим также, что так как операции + и * коммутативны,
 порядок, в котором объединяются извлеченные операнды, несу-
 щественен, но в случае операций - и / необходимо различать
 левый и правый операнды.
     
                           - 85 -
     
     Упражнение 4-3.
     ---------------
     Приведенная основная схема допускает непосредственное
 расширение возможностей калькулятора. Включите операцию де-
 ления по модулю /%/ и унарный минус. Включите команду "сте-
 реть", которая удаляет верхний элемент стека. Введите коман-
 ды для работы с переменными. /Это просто, если имена пере-
 менных будут состоять из одной буквы из имеющихся двадцати
 шести букв/.
 
      4.5. Правила, определяющие область действия.
 
     Функции и внешние переменные, входящие в состав
 "C"-программы, не обязаны компилироваться одновременно;
 программа на исходном языке может располагаться в нескольких
 файлах, и ранее скомпилированные процедуры могут загружаться
 из библиотек. Два вопроса представляют интерес:
     Как следует составлять описания, чтобы переменные пра-
 вильно воспринимались во время компиляции ?
     Как следует составлять описания, чтобы обеспечить пра-
 вильную связь частей программы при загрузке ?
 
     4.5.1. Область действия.
 
     Областью действия имени является та часть программы, в
 которой это имя определено. Для автоматической переменной,
 описанной в начале функции, областью действия является та
 функция, в которой описано имя этой переменной, а переменные
 из разных функций, имеющие одинаковое имя, считаются не от-
 носящимися друг к другу. Это же справедливо и для аргументов
 функций.
     Область действия внешней переменной простирается от точ-
 ки, в которой она объявлена в исходном файле, до конца этого
 файла. Например, если VAL, SP, PUSH, POP и CLEAR определены
 в одном файле в порядке, указанном выше, а именно:
 
      INT  SP = 0;
      DOUBLE  VAL[MAXVAL];
 
      DOUBLE  PUSH(F) {...}
 
      DOUBLE  POP()  {...}
 
      CLEAR()  {...}
 
 то переменные VAL и SP можно использовать в PUSH, POP и
 CLEAR прямо по имени; никакие дополнительные описания не
 нужны.
     С другой стороны, если нужно сослаться на внешнюю пере-
 менную до ее определения, или если такая переменная опреде-
 лена в файле, отличном от того, в котором она используется,
 то необходимо описание EXTERN.
     
                           - 86 -
     
     Важно различать описание внешней переменной и ее опреде-
 ление. описание указывает свойства переменной /ее тип, раз-
 мер и т.д./; определение же вызывает еще и отведение памяти.
 Если вне какой бы то ни было функции появляются строчки
 
    INT  SP;
    DOUBLE  VAL[MAXVAL];
 
 то они определяют внешние переменные SP и VAL, вызывают от-
 ведение памяти для них и служат в качестве описания для ос-
 тальной части этого исходного файла. В то же время строчки
 
    EXTERN  INT  SP;
    EXTERN  DOUBLE  VAL[];
 
 описывают в остальной части этого исходного файла переменную
 SP как INT, а VAL как массив типа DOUBLE /размер которого
 указан в другом месте/, но не создают переменных и не отво-
 дят им места в памяти.
     Во всех файлах, составляющих исходную программу, должно
 содержаться только одно определение внешней переменной; дру-
 гие файлы могут содержать описания EXTERN для доступа к ней.
 /Описание EXTERN может иметься и в том файле, где находится
 определение/. Любая инициализация внешней переменной прово-
 дится только в определении. В определении должны указываться
 размеры массивов, а в описании EXTERN этого можно не делать.
     Хотя подобная организация приведенной выше программы и
 маловероятна, но VAL и SP могли бы быть определены и инициа-
 лизированы в одном файле, а функция PUSH, POP и CLEAR опре-
 делены в другом. В этом случае для связи были бы необходимы
 следующие определения и описания:
 
 в файле 1:
 ----------
 
    INT SP = 0;  /* STACK POINTER */
    DOUBLE VAL[MAXVAL]; /* VALUE STACK */
 
  в файле 2:
  ----------
 
     EXTERN INT SP;
     EXTERN DOUBLE VAL[];
 
     DOUBLE PUSH(F)  {...}
 
     DOUBLE POP()   {...}
 
     CLEAR()   {...}
 
 
 так как описания EXTERN 'в файле 1' находятся выше и вне
 трех указанных функций, они относятся ко всем ним; одного
 набора описаний достаточно для всего 'файла 2'.
     
                           - 87 -
     
     Для программ большого размера обсуждаемая позже в этой
 главе возможность включения файлов, #INCLUDE, позволяет
 иметь во всей программе только одну копию описаний EXTERN и
 вставлять ее в каждый исходный файл во время его компиляции.
     Обратимся теперь к функции GETOP, выбирающей из файла
 ввода следующую операцию или операнд. Основная задача прос-
 та: пропустить пробелы, знаки табуляции и новые строки. Если
 следующий символ отличен от цифры и десятичной точки, то
 возвратить его. В противном случае собрать строку цифр /она
 может включать десятичную точку/ и возвратить NUMBER как
 сигнал о том, что выбрано число.
     Процедура существенно усложняется, если стремиться пра-
 вильно обрабатывать ситуацию, когда вводимое число оказыва-
 ется слишком длинным. Функция GETOP считывает цифры подряд
 /возможно с десятичной точкой/ и запоминает их, пока после-
 довательность не прерывается. Если при этом не происходит
 переполнения, то функция возвращает NUMBER и строку цифр.
 Если же число оказывается слишком длинным, то GETOP отбрасы-
 вает остальную часть строки из файла ввода, так что пользо-
 ватель может просто перепечатать эту строку с места ошибки;
 функция возвращает TOOBIG как сигнал о переполнении.
 
  GETOP(S, LIM) /* GET NEXT OPRERATOR OR OPERAND */
  CHAR S[];
  INT LIM;
  {
    INT I, C;
 
      WHILE((C=GETCH())==' '\!\! C=='\T' \!\! C=='\N')
     ;
    IF (C != '.' && (C  '9'))
     RETURN(C);
    S[0] = C;
    FOR(I=1; (C=GETCHAR()) >='0' && C ='0' && C 0) ? BUF[--BUFP] : GETCHAR());
  }
 
      UNGETCH(C)  /* PUSH CHARACTER BACK ON INPUT */
  INT C;
  {
     IF (BUFP > BUFSIZE)
   PRINTF("UNGETCH: TOO MANY CHARACTERS\N");
     ELSE
   BUF [BUFP++] = C;
  }
 
 Мы использовали для хранения возвращаемых символов массив, а
 не отдельный символ, потому что такая общность может приго-
 диться в дальнейшем.
     
                           - 89 -
     
     
     Упражнение  4-4.
     ----------------
 Напишите функцию UNGETS(S) , которая будет возвращать во
 ввод целую строку. Должна ли UNGETS иметь дело с BUF и BUFP
 или она может просто использовать UNGETCH ?
     Упражнение  4-5.
     ----------------
 Предположите, что может возвращаться только один символ. Из-
 мените GETCH и UNGETCH соответствующим образом.
     Упражнение  4-6.
     ----------------
 Наши функции GETCH и UNGETCH не обеспечивают обработку возв-
 ращенного символа EOF переносимым образом. Решите, каким
 свойством должны обладать эти функции, если возвращается
 EOF, и реализуйте ваши выводы.
 
      4.6. Статические переменные.
 
     Статические переменные представляют собой третий класс
 памяти, в дополнении к автоматическим переменным и EXTERN, с
 которыми мы уже встречались.
     Статические переменные могут быть либо внутренними, либо
 внешними. Внутренние статические переменные точно так же,
 как и автоматические, являются локальными для некоторой фун-
 кции, но, в отличие от автоматических, они остаются сущест-
 вовать, а не появляются и исчезают вместе с обращением к
 этой функции. это означает, что внутренние статические пере-
 менные обеспечивают постоянное, недоступное извне хранение
 внутри функции. Символьные строки, появляющиеся внутри функ-
 ции, как, например, аргументы PRINTF , являются внутренними
 статическими.
     Внешние статические переменные определены в остальной
 части того исходного файла, в котором они описаны, но не в
 каком-либо другом файле. Таким образом, они дают способ
 скрывать имена, подобные BUF и BUFP в комбинации
 GETCH-UNGETCH, которые в силу их совместного использования
 должны быть внешними, но все же не доступными для пользова-
 телей GETCH и UNGETCH , чтобы исключалась возможность конф-
 ликта. Если эти две функции и две переменные объеденить в
 одном файле следующим образом
 
 STATIC CHAR BUF[BUFSIZE]; /* BUFFER FOR UNGETCH */
 STATIC INT BUFP=0; /*NEXT FREE POSITION IN BUF */
 
 GETCH()  {...}
 
 UNGETCH()  {...}
 
 то никакая другая функция не будет в состоянии обратиться к
 BUF и BUFP; фактически, они не будут вступать в конфликт с
 такими же именами из других файлов той же самой программы.
     Статическая память, как внутренняя, так и внешняя, спе-
 цифицируется словом STATIC , стоящим перед обычным описани-
 ем. Переменная является внешней, если она описана вне какой
 бы то ни было функции, и внутренней, если она описана внутри
 некоторой функции.
     
                           - 90 -
     
     Нормально функции являются внешними объектами; их имена
 известны глобально. возможно, однако, объявить функцию как
 STATIC ; тогда ее имя становится неизвестным вне файла, в
 котором оно описано.
     В языке "C" "STATIC" отражает не только постоянство, но
 и степень того, что можно назвать "приватностью". Внутренние
 статические объекты определены только внутри одной функции;
 внешние статические объекты /переменные или функции/ опреде-
 лены только внутри того исходного файла, где они появляются,
 и их имена не вступают в конфликт с такими же именами пере-
 менных и функций из других файлов.
     Внешние статические переменные и функции предоставляют
 способ организовывать данные и работающие с ними внутренние
 процедуры таким образом, что другие процедуры и данные не
 могут прийти с ними в конфликт даже по недоразумению. Напри-
 мер, функции GETCH и UNGETCH образуют "модуль" для ввода и
 возвращения символов; BUF и BUFP должны быть статическими,
 чтобы они не были доступны извне. Точно так же функции PUSH,
 POP и CLEAR формируют модуль обработки стека; VAR и SP тоже
 должны быть внешними статическими.
 
      4.7. Регистровые переменные.
 
     Четвертый и последний класс памяти называется регистро-
 вым. Описание REGISTER указывает компилятору, что данная пе-
 ременная будет часто использоваться. Когда это возможно, пе-
 ременные, описанные как REGISTER, располагаются в машинных
 регистрах, что может привести к меньшим по размеру и более
 быстрым программам. Описание REGISTER выглядит как
 
  REGISTER INT X;
  REGISTER CHAR C;
 
 и т.д.; часть INT может быть опущена. Описание REGISTER мож-
 но использовать только для автоматических переменных и фор-
 мальных параметров функций. В этом последнем случае описания
 выглядят следующим образом:
 
  F(C,N)
  REGISTER INT C,N;
  {
     REGISTER INT I;
     ...
  }
           
                           - 91 -
 
     На практике возникают некоторые ограничения на регистро-
 вые переменные, отражающие реальные возможности имеющихся
 аппаратных средств. В регистры можно поместить только нес-
 колько переменных в каждой функции, причем только определен-
 ных типов. В случае превышения возможного числа или исполь-
 зования неразрешенных типов слово REGISTER игнорируется.
 Кроме того невозможно извлечь адрес регистровой переменной
 (этот вопрос обсуждается в главе 5). Эти специфические огра-
 ничения варьируются от машины к машине. Так, например, на
 PDP-11 эффективными являются только первые три описания
 REGISTER в функции, а в качестве типов допускаются INT, CHAR
 или указатель.
 
      4.8. Блочная структура.
 
     Язык "C" не является языком с блочной структурой в смыс-
 ле PL/1 или алгола; в нем нельзя описывать одни функции
 внутри других.
     Переменные же, с другой стороны, могут определяться по
 методу блочного структурирования. Описания переменных (вклю-
 чая инициализацию) могут следовать за левой фигурной скоб-
 кой,открывающей любой оператор, а не только за той, с кото-
 рой начинается тело функции. Переменные, описанные таким об-
 разом, вытесняют любые переменные из внешних блоков, имеющие
 такие же имена, и остаются определенными до соответствующей
 правой фигурной скобки. Например в
 
 IF (N > 0)  {
    INT I;  /* DECLARE A NEW I */
    FOR (I = 0; I  0); /* DISCARD IT */
     WHILE (--I >= 0)
        PUTCHAR(S[I]);
   }
    
 
     Альтернативой этому способу является рекурсивное реше-
 ние, когда при каждом вызове функция PRINTD сначала снова
 обращается к себе, чтобы скопировать лидирующие цифры, а за-
 тем печатает последнюю цифру.
 
  PRINTD(N)   /* PRINT N IN DECIMAL (RECURSIVE)*/
  INT N;
   (
    INT I;
    
    IF (N  0) THEN
    BEGIN
            A = 1;
            B = 2
    END
 
     Имеется также возможность определения макроса с аргумен-
 тами, так что заменяющий текст будет зависеть от вида обра-
 щения к макросу. Определим, например, макрос с именем MAX
 следующим образом:
 
 #DEFINE MAX(A, B)  ((A) > (B) ? (A) : (B))
 
 когда строка
 
 X = MAX(P+Q, R+S);
 
 будет заменена строкой
 
 X = ((P+Q) > (R+S) ? (P+Q) : (R+S));
 
 Такая возможность обеспечивает "функцию максимума", которая
 расширяется в последовательный код, а не в обращение к функ-
 ции. При правильном обращении с аргументами такой макрос бу-
 дет работать с любыми типами данных; здесь нет необходимости
 в различных видах MAX для данных разных типов, как это было
 бы с функциями.
     
                           - 97 -
     
     Конечно, если вы тщательно рассмотрите приведенное выше
 расширение MAX, вы заметите определенные недостатки. Выраже-
 ния вычисляются дважды; это плохо, если они влекут за собой
 побочные эффекты, вызванные, например, обращениями к функци-
 ям или использованием операций увеличения. Нужно позаботить-
 ся о правильном использовании круглых скобок, чтобы гаранти-
 ровать сохранение требуемого порядка вычислений. (Рассмотри-
 те макрос
 
   #DEFINE SQUARE(X)  X * X
 
 при обращении к ней, как SQUARE(Z+1)). Здесь возникают даже
 некоторые чисто лексические проблемы: между именем макро и
 левой круглой скобкой, открывающей список ее аргументов, не
 должно быть никаких пробелов.
     Тем не менее аппарат макросов является весьма ценным.
 Один практический пример дает описываемая в главе 7 стандар-
 тная библиотека ввода-вывода, в которой GETCHAR и PUTCHAR
 определены как макросы (очевидно PUTCHAR должна иметь аргу-
 мент), что позволяет избежать затрат на обращение к функции
 при обработке каждого символа.
     Другие возможности макропроцессора описаны в приложении
 А.
     Упражнение 4-9.
     ---------------
     Определите макрос SWAP(X, Y), который обменивает значе-
 ниями два своих аргумента типа INT. (В этом случае поможет
 блочная структура).

[ Назад ] [ Далее ]

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог