RuLibrary.com

ГЛАВНАЯ | ПОИСК | ТОП | КАРТА САЙТА      

 
 


 

Керниган Ричи >> Язык C (страница 5)


Ниже приводится функция 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)

Название книги: Язык C
Автор: Керниган Ричи
Просмотрено 45065 раз

123456789101112131415161718192021222324252627282930313233


 
Page generation 0.003 seconds