Пример 5-1. После того, как мы потратили столько усилий на разработку, написание программы в деталях не представляет затруднений. Ниже приводится целиком вся программа, так что вы можете видеть, как соединяются вместе отдельные части. Комбинация символов, по которой производится поиск, выступает пока в качестве символьной строки в аргументе функции index, что не является самым общим механизмом. Мы скоро вернемся к обсуждению вопроса об инициализации символьных массивов и в главе 6 покажем, как сделать комбинацию символов параметром, которому присваивается значение в ходе выполнения программы. Программа также содержит новый вариант функции getline; вам может оказаться полезным сравнить его с вариантом из главы 2.
#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);
}
// Поместить строку в s и возвратить длину
int getline(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);
}
// Вернуть индекс t в s, или -1 в противном случае
int index(char s[], char 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»-программ, расположенных в нескольких исходных файлах, меняется от системы к системе.
1. В системе Unix, например, эту работу выполняет команда cc, упомянутая в главе 2. Предположим, что три функции находятся в трех различных файлах с именами main.с, getline.c и index.с . Тогда команда:
cc main.c getline.c index.c
компилирует эти три файла, помещает полученный настраиваемый объектный код в файлы main.o, getline.o и index.o и загружает их всех в выполняемый файл, называемый a.out .
Если имеется какая-то ошибка, скажем в main.c, то этот файл можно перекомпилировать отдельно и загрузить вместе с предыдущими объектными файлами по команде
cc main.c getline.o index.o
Команда cc использует соглашение о наименовании: суффиксы «.с» и «.о» для того, чтобы отличить исходные файлы от объектных.
2. В системe Windous XP при использовании оболочки Visual Studio и среды программирования Visual C++ используется прогрессивный «проектный» подход: в состав «рабочего пространства проекта» – Workspace в число исходных файлов – Source Files (см. рис. 1.1) нужно включить только исходные модули с суффиксами «.сpp»: main.сpp, getline.cpp и index.сpp. Решение о том, что нужно перекомпилировать, а что не нужно, – принимает оболочка Visual Studio, причем без дополнительного набора каких-либо команд, как этого, например, требует Unix.
Упражнение 5.1. Составьте программу для функции rindex(s,t), которая возвращает позицию самого правого вхождения t в s, и –1, если s не содержит t.
5.2. Функции, возвращающие нецелые значения
До сих пор ни одна из наших программ не содержала какого-либо описания типа функции. Дело в том, что по умолчанию функция неявно описывается своим появлением в выражении или операторе, как, например, в
while (getline(line, maxline) > 0)
Если некоторое имя, которое не было описано ранее, появляется в выражении и за ним следует левая круглая скобка, то оно по контексту считается именем некоторой функции. Кроме того, по умолчанию предполагается, что эта функция возвращает значение типа int. Так как в выражениях char преобразуется в int, то нет необходимости описывать функции, возвращающие char. Эти предположения покрывают большинство случаев, включая все приведенные до сих пор примеры.
Но что происходит, если функция должна возвратить значение какого-то другого типа? Многие численные функции, такие как sqrt, sin и cos возвращают double – значение с плавающей точкой, с двойной точностью. Другие специальные функции возвращают значения других типов. Чтобы показать, как поступать в этом случае, давайте напишем и используем функцию atоf(s), которая преобразует строку s в эквивалентное ей плавающее число двойной точности.
Пример 5-2. Функция atоf является расширением атоi, варианты которой мы написали в главах 3 и 4; она обрабатывает необязательно знак и десятичную точку, а также целую и дробную часть, каждая из которых может как присутствовать, так и отсутствовать (эта процедура преобразования ввода не очень высокого качества; иначе она бы заняла больше места, чем нам хотелось бы).
Во-первых, сама atоf должна описывать тип возвращаемого ею значения, поскольку он отличен от int. Так как в выражениях тип float преобразуется в double, то нет никакого смысла в том, чтобы atоf возвращала float; мы можем с равным успехом воспользоваться дополнительной точностью, так что мы полагаем, что возвращаемое значение типа double. Имя типа должно стоять перед именем функции, как показывается ниже:
// Преобразование строки s в число с двойной точностью
double atof(char s[])
{
double val, power;
int i, sign;
for(i=0;s[i]==' ' || s[i]=='\n' || s[i]=='\t'; i++)
; // Игнорирование символов-разделителей
sign = 1;
if (s[i] == '+' || s[i] == '-') // Знак числа
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);
}
Пример 5-3. Вторым, но столь же важным, является то, что вызывающая функция должна объявить о том, что atof возвращает значение, отличное от int типа. Такое объявление демонстрируется на примере следующего примитивного настольного калькулятора (едва пригодного для подведения баланса в чековой книжке), который считывает по одному числу на строку, причем это число может иметь знак, и складывает все числа, печатая сумму после каждого ввода.
#define maxline 100
main() // Примитивный калькулятор
{
double sum, atof();
char line[maxline];
sum = 0;
while (getline(line, maxline) > 0)
printf("\t=%3.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(char s[]) // Преобразование строки в целое число
{
double atof();
return(atof(s));
}
Обратите внимание на структуру описаний и оператор return. Значение выражения в конструкции:
return (выражение)
всегда преобразуется к типу функции перед выполнением самого возвращения. Поэтому при появлении в операторе return значение функции atof, имеющее тип double, автоматически преобразуется в int, поскольку функция atoi возвращает int. (Как обсуждалось в главе 3, преобразование значения с плавающей точкой к типу int осуществляется посредством отбрасывания дробной части).
Упражнение 5-2. Расширьте atof таким образом, чтобы она могла работать с числами вида 123.45е-6 где за числом с плавающей точкой может следовать «е» и показатель «экспоненты», возможно – со знаком.