“Пока имеется еще строка” - это 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);
}
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 () { } которая не совершает никаких действий.
/Такая ничего не делающая функция иногда оказывается удобной для сохранения места для дальнейшего развития программы/. если функция возвращает что-либо отличное от целого значения, то перед ее именем может стоять указатель типа;
этот вопрос обсуждается в следующем разделе.
Программой является просто набор определений отдельных функций. Связь между функциями осуществляется через аргументы и возвращаемые функциями значения /в этом случае/; ее можно также осуществлять через внешние переменные. Функции могут располагаться в исходном файле в любом порядке, а сама исходная программа может размещаться на нескольких файлах, но так, чтобы ни одна функция не расщеплялась.
Оператор 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] <= '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 мы уже обсуждали тот факт , что аргументы функций передаются по значению, т.е. вызванная функция получает свою временную копию каждого аргумента, а не его адрес. это означает, что вызванная функция не может воздействовать на исходный аргумент в вызывающей функции. Внутри функции каждый аргумент по существу является локальной переменной, которая инициализируется тем значением, с которым к этой функции обратились.