Мы поможем в написании ваших работ!
ЗНАЕТЕ ЛИ ВЫ?
|
Функции, возвращающие нецелые значения
До сих пор ни одна из наших программ не содержала како-го-либо описания типа функции. Дело в том, что по умолчаниюфункция неявно описывается своим появлением в выражении илиоператоре, как, например, в 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 100MAIN() /* 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' и пока-затель экспоненты, возможно со знаком.
Еще об аргументах функций
В главе 1 мы уже обсуждали тот факт, что аргументы фун-кций передаются по значению, т.е. вызванная функция получаетсвою временную копию каждого аргумента, а не его адрес. этоозначает, что вызванная функция не может воздействовать наисходный аргумент в вызывающей функции. Внутри функции каж-дый аргумент по существу является локальной переменной, ко-торая инициализируется тем значением, с которым к этой функ-ции обратились. Если в качестве аргумента функции выступает имя массива,то передается адрес начала этого массива; сами элементы некопируются. Функция может изменять элементы массива, исполь-зуя индексацию и адрес начала. Таким образом, массив переда-ется по ссылке. В главе 5 мы обсудим, как использование ука-зателей позволяет функциям воздействовать на отличные отмассивов переменные в вызывающих функциях. Между прочим, несуществует полностью удовлетворительногоспособа написания переносимой функции с переменным числомаргументов. Дело в том, что нет переносимого способа, с по-мощью которого вызванная функция могла бы определить, сколь-ко аргументов было фактически передано ей в данном обраще-нии. Таким образом, вы, например, не можете написать дейст-вительно переносимую функцию, которая будет вычислять макси-мум от произвольного числа аргументов, как делают встроенныефункции MAX в фортране и PL/1. Обычно со случаем переменного числа аргументов безопасноиметь дело, если вызванная функция не использует аргументов,которые ей на самом деле не были переданы, и если типы сог-ласуются. Самая распространенная в языке "C" функция с пере-менным числом - PRINTF. Она получает из первого аргументаинформацию, позволяющую определить количество остальных ар-гументов и их типы. Функция PRINTF работает совершенно неп-равильно, если вызывающая функция передает ей недостаточноеколичество аргументов, или если их типы не согласуются с ти-пами, указанными в первом аргументе. Эта функция не являетсяпереносимой и должна модифицироваться при использовании вразличных условиях. Если же типы аргументов известны, то конец списка аргу-ментов можно отметить, используя какое-то соглашение; напри-мер, считая, что некоторое специальное значение аргумента(часто нуль) является признаком конца аргументов.
Внешние переменные
Программа на языке "C" состоит из набора внешних объек-тов, которые являются либо переменными, либо функциями. Тер-мин "внешний" используется главным образом в противопостав-ление термину "внутренний", которым описываются аргументы иавтоматические переменные, определенные внурти функций.Внешние переменные определены вне какой-либо функции и, та-ким образом, потенциально доступны для многих функций. Самифункции всегда являются внешними, потому что правила языка"C" не разрешают определять одни функции внутри других. Поумолчанию внешние переменные являются также и "глобальными",так что все ссылки на такую переменную, использующие одно ито же имя (даже из функций, скомпилированных независимо),будут ссылками на одно и то же. В этом смысле внешние пере-менные аналогичны переменным COмMON в фортране и EXTERNAL вPL/1. Позднее мы покажем, как определить внешние переменныеи функции таким образом, чтобы они были доступны не глобаль-но, а только в пределах одного исходного файла. В силу своей глобальной доступности внешние переменныепредоставляют другую, отличную от аргументов и возвращаемыхзначений, возможность для обмена данными между функциями.Если имя внешней переменной каким-либо образом описано, толюбая функция имеет доступ к этой переменной, ссылаясь к нейпо этому имени. В случаях, когда связь между функциями осуществляется спомощью большого числа переменных, внешние переменные оказы-ваются более удобными и эффективными, чем использованиедлинных списков аргументов. Как, однако, отмечалось в главе1, это соображение следует использовать с определенной осто-рожностью, так как оно может плохо отразиться на структурепрограмм и приводить к программам с большим числом связей поданным между функциями. Вторая причина использования внешних переменных связанас инициализацией. В частности, внешние массивы могут бытьинициализированы а автоматические нет. Мы рассмотрим вопрособ инициализации в конце этой главы. Третья причина использования внешних переменных обуслов-лена их областью действия и временем существования. Автома-тические переменные являются внутренними по отношению к фун-кциям; они возникают при входе в функцию и исчезают при вы-ходе из нее. Внешние переменные, напротив, существуют посто-янно. Они не появляютя и не исчезают, так что могут сохра-нять свои значения в период от одного обращения к функции додругого. В силу этого, если две функции используют некоторыеобщие данные, причем ни одна из них не обращается к другой,то часто наиболее удобным оказывается хранить эти общие дан-ные в виде внешних переменных, а не передавать их в функциюи обратно с помощью аргументов. Давайте продолжим обсуждение этого вопроса на большомпримере. Задача будет состоять в написании другой программыдля калькулятора, лучшей,чем предыдущая. Здесь допускаютсяоперации +,-,*,/ и знак = (для выдачи ответа).вместо инфикс-ного представления калькулятор будет использовать обратнуюпольскую нотацию,поскольку ее несколько легче реализовать.вобратной польской нотации знак следует за операндами; инфик-сное выражение типа (1-2)*(4+5)= записывается в виде 12-45+*= круглые скобки при этом не нужны Реализация оказывается весьма простой.каждый операнд по-мещается в стек; когда поступает знак операции,нужное числооперандов (два для бинарных операций) вынимается,к ним при-меняется операция и результат направляется обратно встек.так в приведенном выше примере 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 * 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 */ INT SP = 0; /* STACK POINTER */ DOUBLE VAL[MAXVAL]; /*VALUE STACK */ DOUBLE PUSH(F) /* PUSH F ONTO VALUE STACK */ DOUBLE F; /(IF (SP < MAXVAL) RETURN(VAL[SP++] =F); ELSE /(PRINTF("ERROR: STACK FULL\N"); CLEAR(); RETURN(0); /) /) DOUBLE POP() /* POP TOP VALUE FROM STEACK */ /(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()); для того, чтобы проанализировать верхний элемент стека, неизменяя его. Отметим также, что так как операции + и * коммутативны,порядок, в котором объединяются извлеченные операнды, несу-щественен, но в случае операций - и / необходимо различатьлевый и правый операнды. Упражнение 4-3 --------------- Приведенная основная схема допускает непосредственноерасширение возможностей калькулятора. Включите операцию де-ления по модулю /%/ и унарный минус. Включите команду "сте-реть", которая удаляет верхний элемент стека. Введите коман-ды для работы с переменными. /Это просто, если имена пере-менных будут состоять из одной буквы из имеющихся двадцатишести букв/.
|