}
Каждая функция имеет вид имя (список аргументов, если они имеются) описания аргументов,
если они имеются
{
описания и операторы , если они имеются
}
Как и указывается, некоторые части могут отсутство- вать; минимальной функцией
является
DUMMY () { }
которая не совершает никаких действий.
/Такая ничего не делающая функция иногда оказывается удобной для сохранения места
для дальнейшего развития прог- раммы/. если функция возвращает что-либо отличное
от целого значения, то перед ее именем может стоять указатель типа; этот вопрос
обсуждается в следующем разделе.
Программой является просто набор определений отдельных функций. Связь между функциями
осуществляется через аргумен- ты и возвращаемые функциями значения /в этом случае/;
ее можно также осуществлять через внешние переменные. Функции могут располагаться
в исходном файле в любом порядке, а сама исходная программа может размещаться на
нескольких файлах, но так, чтобы ни одна функция не расщеплялась.
Оператор 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.
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;
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 типа. Такое объявление демонстрируется
на примере следующего примитивного настольного калькулятора /едва пригодного для
подведения баланса в чековой книжке/, который считывает по одному числу на строку,
причем это чис- ло может иметь знак, и складывает все числа, печатая сумму после
каждого ввода.
#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 значе- ние функции атоF, имеющее тип DOUBLE, автоматически
преобра- зуется в INT, поскольку функция ATOI возвращает INT. (Как обсуждалось в
главе 2, преобразование значения с плавающей точкой к типу INT осуществляется посредством
отбрасывания дробной части).
Упражнение 4-2
Расширьте ATOF таким образом, чтобы она могла работать с числами вида
123.45е-6
где за числом с плавающей точкой может следовать 'E' и пока- затель экспоненты,
возможно со знаком.
4.3. Еще об аргументах функций
В главе 1 мы уже обсуждали тот факт , что аргументы фун- кций передаются по значению,
т.е. вызванная функция получает свою временную копию каждого аргумента, а не его
адрес. это означает, что вызванная функция не может воздействовать на исходный аргумент
в вызывающей функции. Внутри функции каж- дый аргумент по существу является локальной
переменной, ко- торая инициализируется тем значением, с которым к этой функ- ции
обратились.
Если в качестве аргумента функции выступает имя массива, то передается адрес
начала этого массива; сами элементы не копируются. Функция может изменять элементы
массива, исполь- зуя индексацию и адрес начала. Таким образом, массив переда- ется
по ссылке. В главе 5 мы обсудим, как использование ука- зателей позволяет функциям
воздействовать на отличные от массивов переменные в вызывающих функциях.
Между прочим, несуществует полностью удовлетворительного способа написания переносимой
функции с переменным числом аргументов. Дело в том, что нет переносимого способа,
с по- мощью которого вызванная функция могла бы определить, сколь- ко аргументов
было фактически передано ей в данном обраще- нии. Таким образом, вы, например, не
можете написать дейст- вительно переносимую функцию, которая будет вычислять макси-
мум от произвольного числа аргументов, как делают встроенные функции MAX в фортране
и PL/1.
Обычно со случаем переменного числа аргументов безопасно иметь дело, если вызванная
функция не использует аргументов, которые ей на самом деле не были переданы, и если
типы сог- ласуются. Самая распространенная в языке "C" функция с пере- менным числом
- PRINTF . Она получает из первого аргумента информацию, позволяющую определить
количество остальных ар- гументов и их типы. Функция PRINTF работает совершенно
неп- равильно, если вызывающая функция передает ей недостаточное количество аргументов,
или если их типы не согласуются с ти- пами, указанными в первом аргументе. Эта функция
не является переносимой и должна модифицироваться при использовании в различных
условиях.
Если же типы аргументов известны, то конец списка аргу- ментов можно отметить,
используя какое-то соглашение; напри- мер, считая, что некоторое специальное значение
аргумента (часто нуль) является признаком конца аргументов.
4.4. Внешние переменные
Программа на языке "C" состоит из набора внешних объек- тов, которые являются
либо переменными, либо функциями. Тер- мин "внешний" используется главным образом
в противопостав- ление термину "внутренний", которым описываются аргументы и автоматические
переменные, определенные внурти функций. Внешние переменные определены вне какой-либо
функции и, та- ким образом, потенциально доступны для многих функций. Сами функции
всегда являются внешними, потому что правила языка "C" не разрешают определять одни
функции внутри других. По умолчанию внешние переменные являются также и "глобальными",
так что все ссылки на такую переменную, использующие одно и то же имя (даже из функций,
скомпилированных независимо), будут ссылками на одно и то же. В этом смысле внешние
пере- менные аналогичны переменным COмMON в фортране и EXTERNAL в PL/1. Позднее
мы покажем, как определить внешние переменные и функции таким образом, чтобы они
были доступны не глобаль- но, а только в пределах одного исходного файла.
В силу своей глобальной доступности внешние переменные предоставляют другую,
отличную от аргументов и возвращаемых значений, возможность для обмена данными между
функциями. Если имя внешней переменной каким-либо образом описано, то любая функция
имеет доступ к этой переменной, ссылаясь к ней по этому имени.
В случаях, когда связь между функциями осуществляется с помощью большого числа
переменных, внешние переменные оказы- ваются более удобными и эффективными, чем
использование длинных списков аргументов. Как, однако, отмечалось в главе 1, это
соображение следует использовать с определенной осто- рожностью, так как оно может
плохо отразиться на структуре программ и приводить к программам с большим числом
связей по данным между функциями.
Вторая причина использования внешних переменных связана с инициализацией. В частности,
внешние массивы могут быть инициализированы а автоматические нет. Мы рассмотрим
вопрос об инициализации в конце этой главы.
Третья причина использования внешних переменных обуслов- лена их областью действия
и временем существования. Автома- тические переменные являются внутренними по отношению
к фун- кциям; они возникают при входе в функцию и исчезают при вы- ходе из нее.
Внешние переменные, напротив, существуют посто- янно. Они не появляютя и не исчезают,
так что могут сохра- нять свои значения в период от одного обращения к функции до
другого. В силу этого, если две функции используют некоторые общие данные, причем
ни одна из них не обращается к другой , то часто наиболее удобным оказывается хранить
эти общие дан- ные в виде внешних переменных, а не передавать их в функцию и обратно
с помощью аргументов.
Давайте продолжим обсуждение этого вопроса на большом примере. Задача будет состоять
в написании другой программы для калькулятора, лучшей,чем предыдущая. Здесь допускаются
операции +,-,*,/ и знак = (для выдачи ответа).вместо инфикс- ного представления
калькулятор будет использовать обратную польскую нотацию,поскольку ее несколько
легче реализовать.в обратной польской нотации знак следует за операндами; инфик-
сное выражение типа
(1-2)*(4+5)=
записывается в виде
12-45+*=
круглые скобки при этом не нужны
Реализация оказывается весьма простой.каждый операнд по- мещается в стек; когда
поступает знак операции,нужное число операндов (два для бинарных операций) вынимается,к
ним при- меняется операция и результат направляется обратно в стек.так в приведенном
выше примере 1 и 2 помещаются в стек и затем заменяются их разностью, -1.после этого
4 и 5 вво- дятся в стек и затем заменяются своей суммой,9.далее числа -1 и 9 заменяются
в стеке на их произведение,равное -9.опе- рация = печатает верхний элемент стека,
не удаляя его (так что промежуточные вычисления могут быть проверены).