явлении конца файла; более общий полезный вариант мог бы пе-
редавать длину строки или нуль, если встретится конец файла.
нуль не может быть длиной строки, так как каждая строка со-
держит по крайней мере один символ; даже строка, содержащая
только символ новой строки, имеет длину 1.
Когда мы находим строку, которая длиннее самой длинной
из предыдущих, то ее надо где-то запомнить. Это наводит на
мысль о другой функции, COPY , которая будет копировать но-
вую строку в место хранения.
Наконец, нам нужна основная программа для управления
функциями GETLINE и COPY . Вот результат :
#DEFINE MAXLINE 1000 /* MAXIMUM INPUT
LINE SIZE */
MAIN() /* FIND LONGEST LINE */
{
INT LEN; /* CURRENT LINE LENGTH */
INT MAX; /* MAXIMUM LENGTH SEEN SO FAR */
CHAR LINE[MAXLINE]; /* CURRENT INPUT LINE */
CHAR SAVE[MAXLINE]; /* LONGEST LINE, SAVED */
MAX = 0;
WHILE ((LEN = GETLINE(LINE, MAXLINE)) > 0)
IF (LEN > MAX) {
MAX = LEN;
COPY(LINE, SAVE);
}
IF (MAX > 0) /* THERE WAS A LINE */
PRINTF(“%S”, SAVE);
}
· 33 -
GETLINE(S,LIM) /* GET LINE INTO S,RETURN LENGTH */
CHAR S[];
INT LIM;
{
INT C, I;
FOR(I=0;I<LIM-1 && (C=GETCHAR())!=EOF && C!='\N';++I)
S[I] = C;
IF (C == '\N') {
S[I] = C;
++I;
}
S[I] = '\0';
RETURN(I);
}
COPY(S1, S2) /* COPY S1 TO S2;
ASSUME S2 BIG ENOUGH */
CHAR S1[], S2[];
{
INT I;
I = 0;
WHILE ((S2[I] = S1[I] != '\0')
++I;
}
Функция MAIN и GETLINE общаются как через пару аргумен-
тов, так и через возвращаемое значение. аргументы GETLINE
описаны в строках
CHAR S[];
INT LIM;
которые указывают, что первый аргумент является массивом, а
второй - целым.
Длина массива S не указана, так как она определена в
MAIN . функция GETLINE использует оператор RETURN для пере-
дачи значения назад в вызывающую программу точно так же, как
это делала функция POWER. Одни функции возвращают некоторое
нужное значение; другие, подобно COPY, используются из-за их
действия и не возвращают никакого значения.
Чтобы пометить конец строки символов, функция GETLINE
помещает в конец создаваемого ей массива символ \0 /нулевой
символ, значение которого равно нулю/. Это соглашение ис-
пользуется также компилятором с языка “C”: когда в “C” -
программе встречается строчная константа типа
“HELLO\N”
·
34 -
то компилятор создает массив символов, содержащий символы
этой строки, и заканчивает его символом \0, с тем чтобы фун-
кции, подобные PRINTF, могли зафиксировать конец массива:
! H ! E ! L ! L ! O ! \N ! \0 !
Спецификация формата %S указывает, что PRINTF ожидает стро-
ку, представленную в такой форме. Проанализировав функцию
COPY, вы обнаружите, что и она опирается на тот факт, что ее
входной аргумент оканчивается символом \0, и копирует этот
символ в выходной аргумент S2. /Все это подразумевает, что
символ \0 не является частью нормального текста/.
Между прочим, стоит отметить, что даже в такой маленькой
программе, как эта, возникает несколько неприятных организа-
ционных проблем. Например, что должна делать MAIN, если она
встретит строку, превышающую ее максимально возможный раз-
мер? Функция GETLINE поступает разумно: при заполнении мас-
сива она прекращает дальнейшее извлечение символов, даже ес-
ли не встречает символа новой строки. Проверив полученную
длину и последний символ, функция MAIN может установить, не
была ли эта строка слишком длинной, и поступить затем, как
она сочтет нужным. Ради краткости мы опустили эту проблему.
Пользователь функции GETLINE никак не может заранее уз-
нать, насколько длинной окажется вводимая строка. Поэтому в
GETLINE включен контроль переполнения. в то же время пользо-
ватель функции COPY уже знает /или может узнать/, каков раз-
мер строк, так что мы предпочли не включать в эту функцию
дополнительный контроль.
Упражнение 1-14.
Переделайте ведущую часть программы поиска самой длинной
строки таким образом, чтобы она правильно печатала длины
сколь угодно длинных вводимых строк и возможно больший
текст.
Упржнение 1-15.
Напишите программу печати всех строк длиннее 80 симво-
лов.
Упражнение 1-16.
Напишите программу, которая будет удалять из каждой
строки стоящие в конце пробелы и табуляции, а также строки,
целиком состоящие из пробелов.
Упражнение 1-17.
Напишите функцию REVERSE(S), которая распологает сим-
вольную строку S в обратном порядке. С ее помощью напишите
программу, которая обратит каждую строку из файла ввода.
·
35 -
1.10. Область действия: внешние переменные.
Переменные в MAIN(LINE, SAVE и т.д.) являются внутренни-
ми или локальными по отношению к функции MAIN, потому что
они описаны внутри MAIN и никакая другая функция не имеет к
ним прямого доступа. Это же верно и относительно переменных
в других функциях; например, переменная I в функции GETLINE
никак не связана с I в COPY. Каждая локальная переменная су-
ществует только тогда, когда произошло обращение к соответс-
твующей функции, и исчезает, как только закончится выполне-
ние этой функции. По этой причине такие переменные, следуя
терминологии других языков, обычно называют автоматическими.
Мы впредь будем использовать термин автоматические при ссыл-
ке на эти динамические локальные переменные. /в главе 4 об-
суждается класс статической памяти, когда локальные перемен-
ные все же оказываются в состоянии сохранить свои значения
между обращениями к функциям/.
Поскольку автоматические переменные появляются и исчеза-
ют вместе с обращением к функции, они не сохраняют своих
значений в промежутке от одного вызова до другого, в силу
чего им при каждом входе нужно явно присваивать значения.
Если этого не сделать, то они будут содержать мусор.
В качестве альтернативы к автоматическим переменным мож-
но определить переменные, которые будут внешними для всех
функций, т.е. Глобальными переменными, к которым может обра-
титься по имени любая функция, которая пожелает это сделать.
(этот механизм весьма сходен с “COMMON” в фортране и
“EXTERNAL” в PL/1). Так как внешние переменные доступны всю-
ду, их можно использовать вместо списка аргументов для пере-
дачи данных между функциями. Кроме того, поскольку внешние
переменные существуют постоянно, а не появляются и исчезают
вместе с вызываемыми функциями, они сохраняют свои значения
и после того, как функции, присвоившие им эти значения, за-
вершат свою работу.
Внешняя переменная должна быть определена вне всех функ-
ций; при этом ей выделяется фактическое место в памяти. Та-
кая переменная должна быть также описана в каждой функции,
которая собирается ее использовать; это можно сделать либо
явным описанием EXTERN, либо неявным по контексту. Чтобы
сделать обсуждение более конкретным, давайте перепишем прог-
рамму поиска самой длинной строки, сделав LINE, SAVE и MAX
внешними переменными. Это потребует изменения описаний и тел
всех трех функций, а также обращений к ним.
#DEFINE MAXLINE 1000 /* MAX. INPUT LINE SIZE*/
CHAR LINE[MAXLINE]; /* INPUT LINE */
CHAR SAVE[MAXLINE];/* LONGEST LINE SAVED HERE*/
INT MAX;/*LENGTH OF LONGEST LINE SEEN SO FAR*/
· 36 -
MAIN() /*FIND LONGEST LINE; SPECIALIZED VERSION*/
{
INT LEN;
EXTERN INT MAX;
EXTERN CHAR SAVE[];
MAX = 0;
WHILE ( (LEN = GETLINE()) > 0 )
IF ( LEN > MAX ) {
MAX = LEN;
COPY();
}
IF ( MAX > 0 ) /* THERE WAS A LINE */
PRINTF( “%S”, SAVE );
}
GETLINE() /* SPECIALIZED VERSION */
{
INT C, I;
EXTERN CHAR LINE[];
FOR (I = 0; I < MAXLINE-1
&& (C=GETCHAR()) !=EOF && C!='\N'; ++I)
LINE[I] = C;
++I;
}
LINE[I] = '\0'
RETURN(I)
}
COPY() /* SPECIALIZED VERSION */
{
INT I;
EXTERN CHAR LINE[], SAVE[];
I = 0;
WHILE ((SAVE[I] = LINE[I]) !='\0')
++I;
}
Внешние переменные для функций MAIN, GETLINE и COPY оп-
ределены в первых строчках приведенного выше примера, кото-
рыми указывается их тип и вызывается отведение для них памя-
ти. синтаксически внешние описания точно такие же, как опи-
сания, которые мы использовали ранее, но так как они распо-
ложены вне функций, соответствующие переменные являются
внешними. Чтобы функция могла использовать внешнюю переме-
ную, ей надо сообщить ее имя. Один способ сделать это -
включить в функцию описание EXTERN; это описание отличается
от предыдущих только добавлением ключевого слова EXTERN.
· 37 -
В определенных ситуациях описание EXTERN может быть опу-
щено: если внешнее определение переменной находится в том же
исходном файле, раньше ее использования в некоторой конкрет-
ной функции, то не обязательно включать описание EXTERN для
этой переменной в саму функцию. Описания EXTERN в функциях
MAIN, GETLINE и COPY являются, таким образом, излишними.
Фактически, обычная практика заключается в помещении опреде-
лений всех внешних переменных в начале исходного файла и
последующем опускании всех описаний EXTERN.
Если программа находится в нескольких исходных файлах, и
некоторая переменная определена, скажем в файле 1, а исполь-
зуется в файле 2, то чтобы связать эти два вхождения пере-
менной, необходимо в файле 2 использовать описание EXTERN.
Этот вопрос подробно обсуждается в главе 4.
Вы должно быть заметили, что мы в этом разделе при ссыл-
ке на внешние переменные очень аккуратно используем слова
описание и определение. “Определение” относится к тому мес-
ту, где переменная фактически заводится и ей выделяется па-
мять; “описание” относится к тем местам, где указывается
природа переменной, но никакой памяти не отводится.
Между прочим, существует тенденция объявлять все, что ни
попадется, внешними переменными, поскольку кажется, что это
упрощает связи, - списки аргументов становятся короче и пе-
ременные всегда присутствуют, когда бы вам они ни понадоби-
лись. Но внешние переменные присутствуют и тогда, когда вы в
них не нуждаетесь. Такой стиль программирования чреват опас-
ностью, так как он приводит к программам, связи данных внут-
ри которых не вполне очевидны. Переменные при этом могут из-
меняться неожиданным и даже неумышленным образом, а програм-
мы становится трудно модифицировать, когда возникает такая