Брайан Керниган - Язык программирования Си. Издание 3-е, исправленное
Когда мы обнаружили строку более длинную, чем самая длинная из всех предыдущих, то нам надо будет где-то ее запомнить. Здесь напрашивается вторая функция, copy, которая умеет копировать новую строку в надежное место.
Наконец, нам необходима главная программа, которая бы управляла функциями getline и copy. Вот как выглядит наша программа в целом:
#include ‹stdio.h›
#define MAXLINE 1000 /* максимальный размер вводимой строки */
int getline(char line[], int MAXLINE);
void copy(char to[], char from[]);
/* печать самой длинной строки */
main()
{
int len; /* длина текущей строки */
int max; /* длина максимальной из просмотренных строк */
char line[MAXLINE]; /* текущая строка */
char longest[MAXLINE]; /* самая длинная строка */
max = 0;
while (len = getline(line, MAXLINE)) › 0)
if (len › max) {
max = len;
copy(longest, line);
}
if (max › 0) /* была ли хоть одна строка? */
printf("%s", longest);
return 0;
}
/* getline: читает строку в s, возвращает длину */
int getline(char s[], int lim)
{
int c, i;
for (i = 0; i ‹ lim-1 && (c = getchar()) != EOF && с != 'n'; ++i)
s[i] = c;
if (c == 'n') {
s[i] = c;
++i;
}
s[i] = ' ';
return i;
}
/* copy: копирует из 'from' в 'to'; to достаточно большой */
void copy(char to[], char from[])
{
int i;
i = 0;
while ((to[i] = from[i]) != ' ')
++i;
}
Мы предполагаем, что функции getline и copy, описанные в начале программы, находятся в том же файле, что и main.
Функции main и getline взаимодействуют между собой через пару аргументов и возвращаемое значение. В getline аргументы определяются строкой
int getline(char s[], int lim);
Как мы видим, ее первый аргумент s есть массив, а второй, lim, имеет тип int. Задание размера массива в определении имеет целью резервирование памяти. В самой getline задавать длину массива s нет необходимости, так как его размер указан в main. Чтобы вернуть значение вызывающей программе, getline использует return точно так же, как это делает функция power. В приведенной строке также сообщается, что getline возвращает значение типа int, но так как при отсутствии указания о типе подразумевается int, то перед getline слово int можно опустить.
Одни функции возвращают результирующее значение, другие (такие как copy) нужны только для того, чтобы произвести какие-то действия, не выдавая никакого значения. На месте типа результата в copy стоит void. Это явное указание на то, что никакого значения данная функция не возвращает.
Функция getline в конец создаваемого ею массива помещает символ ' ' (null-символ, кодируемый нулевым байтом), чтобы пометить конец строки символов. То же соглашение относительно окончания нулем соблюдается и в случае строковой константы вроде
"hellon"
В данном случае для него формируется массив из символов этой строки с ' ' в конце.
h е l l o n
Спецификация %s в формате printf предполагает, что соответствующий ей аргумент - строка символов, оформленная указанным выше образом. Функция copy в своей работе также опирается на тот факт, что читаемый ею аргумент заканчивается символом ' ', который она копирует наряду с остальными символами. (Всё сказанное предполагает, что ' ' не встречается внутри обычного текста.)
Попутно стоит заметить, что при работе даже с такой маленькой программой мы сталкиваемся с некоторыми конструктивными трудностями. Например, что должна делать main, если встретится строка, превышающая допустимый размер? Функция getline работает надежно: если массив полон, она прекращает пересылку, даже если символа новой строки не обнаружила. Получив от getline длину строки и увидев, что она совпадает с MAXLINE, главная программа main могла бы "отловить" этот особый случай и справиться с ним. В интересах краткости описание этого случая мы здесь опускаем.
Пользователи getline не могут заранее узнать, сколь длинными будут вводимые строки, поэтому getline делает проверки на переполнение. А вот пользователям функции copy размеры копируемых строк известны (или они могут их узнать), поэтому дополнительный контроль здесь не нужен.
Упражнение 1.16. Перепишите main предыдущей программы так, чтобы она могла печатать самую длинную строку без каких-либо ограничений на ее размер.
Упражнение 1.17. Напишите программу печати всех вводимых строк, содержащих более 80 символов.
Упражнение 1.18. Напишите программу, которая будет в каждой вводимой строке заменять стоящие подряд символы пробелов и табуляций на один пробел и удалять пустые строки.
Упражнение 1.19. Напишите функцию reverse(s), размещающую символы в строке s в обратном порядке. Примените ее при написании программы, которая каждую вводимую строку располагает в обратном порядке.
1.10 Внешние переменные и область видимости
Переменные line, longest и прочие принадлежат только функции main, или, как говорят, локальны в ней. Поскольку они объявлены внутри main, никакие другие функции прямо к ним обращаться не могут. То же верно и применительно к переменным других функций. Например, i в getline не имеет никакого отношения к i в copy. Каждая локальная переменная функции возникает только в момент обращения к этой функции и исчезает после выхода из нее. Вот почему такие переменные, следуя терминологии других языков, называют автоматическими. (В главе 4 обсуждается класс памяти static, который позволяет локальным переменным сохранять свои значения в промежутках между вызовами.)
Так как автоматические переменные образуются и исчезают одновременно с входом в функцию и выходом из нее, они не сохраняют своих значений от вызова к вызову и должны устанавливаться заново при каждом новом обращении к функции. Если этого не делать, они будут содержать "мусор".
В качестве альтернативы автоматическим переменным можно определить внешние переменные, к которым разрешается обращаться по их именам из любой функции. (Этот механизм аналогичен области COMMON в Фортране и определениям переменных в самом внешнем блоке в Паскале.) Так как внешние переменные доступны повсеместно, их можно использовать вместо аргументов для связи между функциями по данным. Кроме того, поскольку внешние переменные существуют постоянно, а не возникают и исчезают на период выполнения функции, свои значения они сохраняют и после возврата из функций, их установивших.
Внешняя переменная должна быть определена, причем только один раз, вне текста любой функции; в этом случае ей будет выделена память. Она должна быть объявлена во всех функциях, которые хотят ею пользоваться. Объявление содержит сведения о типе переменной. Объявление может быть явным, в виде инструкции extern, или неявным, когда нужная информация получается из контекста. Чтобы конкретизировать сказанное, перепишем программу печати самой длинной строки с использованием line, longest и max в качестве внешних переменных. Это потребует изменений в вызовах, объявлениях и телах всех трех функций.
#include ‹stdio.h›
#define MAXLINE 1000 /* максимальный размер вводимой строки */
int max; /* длина максимальной из просмотренных строк */
char line[MAXLINE]; /* текущая строка */
char longest[MAXLINE]; /* самая длинная строка */
int getline(void);
void copy(void);
/* печать самой длинной строки: специализированная версия */
main()
{
int len;
extern int max;
extern char longest[];
max = 0;
while ((len = getline()) › 0)
if (len › max) {
max = len;
copy();
}
if (max › 0) /* была хотя бы одна строка */
printf("%s", longest);
return 0;
}
/* getline: специализированная версия */
int getline(void)
{
int c, i;
extern char line[];
for (i = 0; i ‹ MAXLINE-1 && (c=getchar()) != EOF && c!= 'n'; ++i)