Смекни!
smekni.com

Если имеется какая-то ошибка, скажем в 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] <= '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(“&bsol;T%.2F&bsol;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, это соображение следует использовать с определенной осто-

рожностью, так как оно может плохо отразиться на структуре

программ и приводить к программам с большим числом связей по

данным между функциями.

Вторая причина использования внешних переменных связана

с инициализацией. В частности, внешние массивы могут быть

инициализированы а автоматические нет. Мы рассмотрим вопрос

об инициализации в конце этой главы.

Третья причина использования внешних переменных обуслов-