Смекни!
smekni.com

явлении конца файла; более общий полезный вариант мог бы пе-

редавать длину строки или нуль, если встретится конец файла.

нуль не может быть длиной строки, так как каждая строка со-

держит по крайней мере один символ; даже строка, содержащая

только символ новой строки, имеет длину 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!='&bsol;N';++I)

S[I] = C;

IF (C == '&bsol;N') {

S[I] = C;

++I;

}

S[I] = '&bsol;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] != '&bsol;0')

++I;

}

Функция MAIN и GETLINE общаются как через пару аргумен-

тов, так и через возвращаемое значение. аргументы GETLINE

описаны в строках

CHAR S[];

INT LIM;

которые указывают, что первый аргумент является массивом, а

второй - целым.

Длина массива S не указана, так как она определена в

MAIN . функция GETLINE использует оператор RETURN для пере-

дачи значения назад в вызывающую программу точно так же, как

это делала функция POWER. Одни функции возвращают некоторое

нужное значение; другие, подобно COPY, используются из-за их

действия и не возвращают никакого значения.

Чтобы пометить конец строки символов, функция GETLINE

помещает в конец создаваемого ей массива символ &bsol;0 /нулевой

символ, значение которого равно нулю/. Это соглашение ис-

пользуется также компилятором с языка “C”: когда в “C” -

программе встречается строчная константа типа

“HELLO&bsol;N”

·
34 -

то компилятор создает массив символов, содержащий символы

этой строки, и заканчивает его символом &bsol;0, с тем чтобы фун-

кции, подобные PRINTF, могли зафиксировать конец массива:

! H ! E ! L ! L ! O ! &bsol;N ! &bsol;0 !

Спецификация формата %S указывает, что PRINTF ожидает стро-

ку, представленную в такой форме. Проанализировав функцию

COPY, вы обнаружите, что и она опирается на тот факт, что ее

входной аргумент оканчивается символом &bsol;0, и копирует этот

символ в выходной аргумент S2. /Все это подразумевает, что

символ &bsol;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!='&bsol;N'; ++I)

LINE[I] = C;

++I;

}

LINE[I] = '&bsol;0'

RETURN(I)

}

COPY() /* SPECIALIZED VERSION */

{

INT I;

EXTERN CHAR LINE[], SAVE[];

I = 0;

WHILE ((SAVE[I] = LINE[I]) !='&bsol;0')

++I;

}

Внешние переменные для функций MAIN, GETLINE и COPY оп-

ределены в первых строчках приведенного выше примера, кото-

рыми указывается их тип и вызывается отведение для них памя-

ти. синтаксически внешние описания точно такие же, как опи-

сания, которые мы использовали ранее, но так как они распо-

ложены вне функций, соответствующие переменные являются

внешними. Чтобы функция могла использовать внешнюю переме-

ную, ей надо сообщить ее имя. Один способ сделать это -

включить в функцию описание EXTERN; это описание отличается

от предыдущих только добавлением ключевого слова EXTERN.

· 37 -

В определенных ситуациях описание EXTERN может быть опу-

щено: если внешнее определение переменной находится в том же

исходном файле, раньше ее использования в некоторой конкрет-

ной функции, то не обязательно включать описание EXTERN для

этой переменной в саму функцию. Описания EXTERN в функциях

MAIN, GETLINE и COPY являются, таким образом, излишними.

Фактически, обычная практика заключается в помещении опреде-

лений всех внешних переменных в начале исходного файла и

последующем опускании всех описаний EXTERN.

Если программа находится в нескольких исходных файлах, и

некоторая переменная определена, скажем в файле 1, а исполь-

зуется в файле 2, то чтобы связать эти два вхождения пере-

менной, необходимо в файле 2 использовать описание EXTERN.

Этот вопрос подробно обсуждается в главе 4.

Вы должно быть заметили, что мы в этом разделе при ссыл-

ке на внешние переменные очень аккуратно используем слова

описание и определение. “Определение” относится к тому мес-

ту, где переменная фактически заводится и ей выделяется па-

мять; “описание” относится к тем местам, где указывается

природа переменной, но никакой памяти не отводится.

Между прочим, существует тенденция объявлять все, что ни

попадется, внешними переменными, поскольку кажется, что это

упрощает связи, - списки аргументов становятся короче и пе-

ременные всегда присутствуют, когда бы вам они ни понадоби-

лись. Но внешние переменные присутствуют и тогда, когда вы в

них не нуждаетесь. Такой стиль программирования чреват опас-

ностью, так как он приводит к программам, связи данных внут-

ри которых не вполне очевидны. Переменные при этом могут из-

меняться неожиданным и даже неумышленным образом, а програм-

мы становится трудно модифицировать, когда возникает такая