Ниже приводится функция POWER и использующая ее основная программа, так что вы
можете видеть целиком всю структуру.
MAIN() /* TEST POWER FUNCTION */
{
INT I;
FOR(I = 0; I < 10; ++I)
PRINTF("%D %D %D\N",I,POWER(2,I),POWER(-3,I));
}
POWER(X,N) /* RAISE X N-TH POWER; N > 0 */
INT X,N;
{
INT I, P;
P = 1;
FOR (I =1; I <= N; ++I)
P = P * X;
RETURN (P);
}
Все функции имеют одинаковый вид:
имя (список аргументов, если они имеются)
описание аргументов, если они имеются
{
описания
операторы
}
Эти функции могут быть записаны в любом порядке и нахо- диться в одном или двух
исходных файлах. Конечно, если ис- ходная программа размещается в двух файлах, вам
придется дать больше указаний при компиляции и загрузке, чем если бы она находилась
в одном, но это дело операционной системы, а не атрибут языка. В данный момент,
для того чтобы все полу- ченные сведения о прогоне "C"- программ, не изменились
в дальнейшем, мы будем предполагать, что обе функции находятся в одном и том же
файле.
Функция POWER вызывается дважды в строке
PRINTF("%D %D %D\N",I,POWER(2,I),POWER(-3,I));
при каждом обращении функция POWER, получив два аргумента, вазвращает целое значение,
которое печатается в заданном формате. В выражениях POWER(2,I) является точно таким
же це- лым, как 2 и I. /Не все функции выдают целое значение; мы займемся этим вопросом
в главе 4/.
Аргументы функции POWER должны быть описаны соответству- ющим образом, так как
их типы известны. Это сделано в строке
INT X,N;
которая следует за именем функции.
Описания аргументов помещаются между списком аргументов и открывающейся левой
фигурной скобкой; каждое описание за- канчивается точкой с запятой. Имена, использованные
для ар- гументов функции POWER, являются чисто локальными и недос- тупны никаким
другим функциям: другие процедуры могут ис- пользовать те же самые имена без возникновения
конфликта. Это верно и для переменных I и P; I в функции POWER никак не связано
с I в функции MAIN.
Значение, вычисленное функцией POWER, передаются в MAIN с помощью оператора RETURN,
точно такого же, как в PL/1. внутри круглых скобок можно написать любое выражение.
Функ- ция не обязана возвращать какое-либо значение; оператор RETURN, не содержащий
никакого выражения, приводит к такой же передаче управления, как "сваливание на
конец" функции при достижении конечной правой фигурной скобки, но при этом в вызывающую
функцию не возвращается никакого полезного зна- чения.
Упражнение 1-13
Апишите программу преобразования прописных букв из айла ввода в строчные, используя
при этом функцию OWER(C), кото- рая возвращает значение 'C', если C'- не буква,
и значение соответствующей строчной уквы, если 'C'-буква.
1.8. Аргументы - вызов по значению
Один аспект в "C" может оказаться непривычным для прог- раммистов, которые использовали
другие языки, в частности, фортран и PL/1. в языке "C" все аргументы функций передаются
"по значению". это означает, что вызванная функция получает значения своих аргументов
с помощью временных переменных /фактически через стек/, а не их адреса. Это приводит
к не- которым особенностям, отличным от тех, с которыми мы сталки- вались в языках
типа фортрана и PL/1, использующих "вызов по ссылке ", где вызванная процедура работает
с адресом аргу- мента, а не с его значением.
Главное отличие состоит в том, что в "C" вызванная функ- ция не может изменить
переменную из вызывающей функции; она может менять только свою собственную временную
копию.
Вызов по значению, однако, не помеха, а весьма ценное качество. Оно обычно приводит
к более компактным программам, содержащим меньше не относящихся к делу переменных,
потому что с аргументами можно обращаться как с удобно инициализи- рованными локальными
перемнными вызванной процедуры. Вот, например, вариант функции POWER использующей
это обстоятель- ство
POWER(X,N) /* RAISE X N-TH POWER; N > 0;
VERSION 2 */
INT X,N;
{
INT P;
FOR (P = 1; N > 0; --N)
P = P * X;
RETURN (P);
}
Аргумент N используется как временная переменная; из не- го вычитается единица
до тех пор, пока он не станет нулем. Переменная I здесь больше не нужна. чтобы ни
происходило с N внутри POWER это никак не влияет на аргумент, с которым пер- воначально
обратились к функции POWER.
При необходимости все же можно добиться, чтобы функция изменила переменную из
вызывающей программы. Эта программа должна обеспечить установление адреса переменной
/техничес- ки, через указатель на переменную/, а в вызываемой функции надо описать
соответствующий аргумент как указатель и ссы- латься к фактической переменной косвенно
через него. Мы рас- смотрим это подробно в главе 5.
Когда в качестве аргумента выступает имя массива, то фактическим значением, передаваемым
функции, является адрес начала массива. /Здесь нет никакого копирования элементов
массива/. С помощью индексации и адреса начала функция может найти и изменить любой
элемент массива. Это - тема следующе- го раздела.
1.9. Массивы символов
По-видимому самым общим типом массива в "C" является массив символов. Чтобы проиллюстрировать
использование мас- сивов символов и обрабатывающих их функций, давайте напишем программу,
которая читает набор строк и печатает самую длин- ную из них. Основная схема программы
достаточно проста:
WHILE (имеется еще строка)
IF (эта строка длиннее самой длинной из
предыдущих)
запомнить эту строку и ее длину напечатать самую длинную строку
По этой схеме ясно, что программа естественным образом распадается на несколько
частей. Одна часть читает новую строку, другая проверяет ее, третья запоминает,
а остальные части программы управляют этим процессом.
Поскольку все так прекрасно делится, было бы хорошо и написать программу соответсвующим
образом. Давайте сначала напишем отдельную функцию GETLINE, которая будет извлекать
следующую строку из файла ввода; это - обобщение функции GETCHAR. мы попытаемся
сделать эту функцию по возможности более гибкой, чтобы она была полезной и в других
ситуациях. Как минимум GETLINE должна передавать сигнал о возможном по- явлении
конца файла; более общий полезный вариант мог бы пе- редавать длину строки или нуль,
если встретится конец файла. нуль не может быть длиной строки, так как каждая строка
со- держит по крайней мере один символ; даже строка, содержащая только символ новой
строки, имеет длину 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);
}
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"
то компилятор создает массив символов, содержащий символы этой строки, и заканчивает
его символом \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 в обратном
порядке. С ее помощью напишите программу, которая обратит каждую строку из файла
ввода.
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*/ 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)