Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Функции, возвращающие нецелые значения
До сих пор ни одна из наших программ не содержала како-го-либо описания типа функции. Дело в том, что по умолчаниюфункция неявно описывается своим появлением в выражении илиоператоре, как, например, в 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 --------------- Приведенная основная схема допускает непосредственноерасширение возможностей калькулятора. Включите операцию де-ления по модулю /%/ и унарный минус. Включите команду "сте-реть", которая удаляет верхний элемент стека. Введите коман-ды для работы с переменными. /Это просто, если имена пере-менных будут состоять из одной буквы из имеющихся двадцатишести букв/.
|
||||
Последнее изменение этой страницы: 2016-08-26; просмотров: 187; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.144.25.74 (0.004 с.) |