RuLibrary.com

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

 
 


 

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


Последней операцией языка "C" является запятая ",", ко- торая чаще всего используется в операторе FOR. Два выраже- ния, разделенные запятой, вычисляются слева направо, причем типом и значением результата являются тип и значение правого операнда. Таким образом, в различные части оператора FOR можно включить несколько выражений, например, для параллель- ного изменения двух индексов. Это иллюстрируется функцией REVERSE(S), которая располагает строку S в обратном порядке на том же месте.

REVERSE(S) /* REVERSE STRING S IN PLACE */

CHAR S[];

{

INT C, I, J;

FOR(I = 0, J = STRLEN(S) - 1; I < J; I++, J--) {

C = S[I];

S[I] = S[J];

S[J] = C;

}

}

Запятые, которые разделяют аргументы функций, переменные в описаниях и т.д., не имеют отношения к операции запятая и не обеспечивают вычислений слева направо.

Упражнение 3-2

Составьте программу для функции EXPAND(S1,S2), которая расширяет сокращенные обозначения вида а-Z из строки S1 в эквивалентный полный список авс...XYZ в S2. Допускаются сок- ращения для строчных и прописных букв и цифр. Будьте готовы иметь дело со случаями типа а-в-с, а-Z0-9 и -а-Z. (Полезное соглашение состоит в том, что символ -, стоящий в начале или конце, воспринимается буквально).

3.6. Цикл DO - WHILE

Как уже отмечалось в главе 1, циклы WHILE и FOR обладают тем приятным свойством, что в них проверка окончания осущес- твляется в начале, а не в конце цикла. Третий оператор цикла языка "C", DO-WHILE, проверяет условие окончания в конце, после каждого прохода через тело цикла; тело цикла всегда выполняется по крайней мере один раз. Синтаксис этого опера- тора имеет вид:

DO

оператор WHILE (выражение)

Сначала выполняется оператор, затем вычисляется выражение. Если оно истинно, то оператор выполняется снова и т.д. Если выражение становится ложным, цикл заканчивается.

Как и можно было ожидать, цикл DO-WHILE используется значительно реже, чем WHILE и FOR, составляя примерно пять процентов от всех циклов. Тем не менее, иногда он оказывает- ся полезным, как, например, в следующей функции ITOA, кото- рая преобразует число в символьную строку (обратная функции ATOI). Эта задача оказывается несколько более сложной, чем может показаться сначала. Дело в том, что простые методы вы- деления цифр генерируют их в неправильном порядке. Мы пред- почли получить строку в обратном порядке, а затем обратить ее. ITOA(N,S) /*CONVERT N TO CHARACTERS IN S */ CHAR S[]; INT N; { INT I, SIGN;

IF ((SIGN = N) < 0) /* RECORD SIGN */

N = -N; /* MAKE N POSITIVE */ I = 0; DO { /* GENERATE DIGITS IN REVERSE ORDER */

S[I++] = N % 10 + '0';/* GET NEXT DIGIT */ } WHILE ((N /=10) > 0); /* DELETE IT */ IF (SIGN < 0)

S[I++] = '-' S[I] = '\0'; REVERSE(S); }

Цикл DO-WHILE здесь необходим, или по крайней мере удобен, поскольку, каково бы ни было значение N, массив S должен со- держать хотя бы один символ. Мы заключили в фигурные скобки один оператор, составляющий тело DO-WHILе, хотя это и не обязательно, для того, чтобы торопливый читатель не принял часть WHILE за начало оператора цикла WHILE.

Упражнение 3-3

При представлении чисел в двоичном дополнительном коде наш вариант ITOA не справляется с наибольшим отрицательным числом, т.е. Со значением N рAвным -2 в степени м-1, где м - размер слова. объясните почему. Измените программу так, что- бы она правильно печатала это значение на любой машине.

Упражнение 3-4

Напишите аналогичную функцию ITOB(N,S), которая преобра- зует целое без знака N в его двоичное символьное представле- ние в S. Запрограммируйте функцию ITOH, которая преобразует целое в шестнадцатеричное представление.

Упражнение 3-5

Напишите вариант Iтоа, который имеет три, а не два аргу- мента. Третий аргумент - минимальная ширина поля; преобразо- ванное число должно, если это необходимо, дополняться слева пробелами, так чтобы оно имело достаточную ширину.

3.7. Оператор BREAK

Иногда бывает удобным иметь возможность управлять выхо- дом из цикла иначе, чем проверкой условия в начале или в конце. Оператор BRеак позволяет выйти из операторов FOR, WHILE и DO до окончания цикла точно так же, как и из перек- лючателя. Оператор BRеак приводит к немедленному выходу из самого внутреннего охватывающего его цикла (или переключате- ля).

Следующая программа удаляет хвостовые пробелы и табуля- ции из конца каждой строки файла ввода. Она использует опе- ратор BRеак для выхода из цикла, когда найден крайний правый отличный от пробела и табуляции символ.

#DEFINE MAXLINE 1000

MAIN() /* REMOVE TRAILING BLANKS AND TABS */

{

INT N;

CHAR LINE[MAXLINE];

WHILE ((N = GETLINE(LINE,MAXLINE)) > 0) {

WHILE (--N >= 0)

IF (LINE[N] != ' ' && LINE[N] != '\T'

&& LINE[N] != '\N')

BREAK;

LINE[N+1] = '\0';

PRINTF("%S\N",LINE);

}

}

Функция GETLINE возвращает длину строки. Внутренний цикл начинается с последнего символа LINE (напомним, что --N уменьшает N до использования его значения) и движется в об- ратном направлении в поиске первого символа , который отли- чен от пробела, табуляции или новой строки. Цикл прерывает- ся, когда либо найден такой символ, либо N становится отри- цательным (т.е., когда просмотрена вся строка). Советуем вам убедиться, что такое поведение правильно и в том случае, когда строка состоит только из символов пустых промежутков.

В качестве альтернативы к BRеак можно ввести проверку в сам цикл:

WHILE ((N = GETLINE(LINE,MAXLINE)) > 0) {

WHILE (--N >= 0

&& (LINE[N] == ' ' \!\! LINE[N] == '\T'

\!\! LINE[N] == '\N'))

;

... }

Это уступает предыдущему варианту, так как проверка стано- вится труднее для понимания. Проверок, которые требуют пе- реплетения &&, \!\!, ! И круглых скобок, по возможности сле- дует избегать.

3.8. Оператор CONTINUE

Оператор CONTINUE родственен оператору BRеак, но исполь- зуется реже; он приводит к началу следующей итерации охваты- вающего цикла (FOR, WHILE, DO ). В циклах WHILE и DO это оз- начает непосредственный переход к выполнению проверочной части; в цикле FOR управление передается на шаг реинициали- зации. (Оператор CONTINUE применяется только в циклах, но не в переключателях. Оператор CONTINUE внутри переключателя внутри цикла вызывает выполнение следующей итерации цикла).

В качестве примера приведем фрагмент, который обрабаты- вает только положительные элементы массива а; отрицательные значения пропускаются.

FOR (I = 0; I < N; I++) {

IF (A[I] < 0) /* SKIP NEGATIVE ELEMENTS */

CONTINUE;

... /* DO POSITIVE ELEMENTS */ }

Оператор CONTINUE часто используется, когда последующая часть цикла оказывается слишком сложной, так что рассмотре- ние условия, обратного проверяемому, приводит к слишком глу- бокому уровню вложенности программы.

Упражнение 3-6

Напишите программу копирования ввода на вывод, с тем ис- ключением, что из каждой группы последовательных одинаковых строк выводится только одна. (Это простой вариант утилиты UNIQ систем UNIX).

3.9. Оператор GOTO и метки

В языке "C" предусмотрен и оператор GOTO, которым беско- нечно злоупотребляют, и метки для ветвления. С формальной точки зрения оператор GOTO никогда не является необходимым, и на практике почти всегда можно обойтись без него. Мы не использовали GOTO в этой книге.

Тем не менее, мы укажем несколько ситуаций, где оператор GOTO может найти свое место. Наиболее характерным является его использование тогда, когда нужно прервать выполнение в некоторой глубоко вложенной структуре, например, выйти сразу из двух циклов. Здесь нельзя непосредственно использовать оператор BRеак, так как он прерывает только самый внутренний цикл. Поэтому:

FOR ( ... )

FOR ( ... ) {

...

IF (DISASTER)

GOTO ERROR;

}

...

ERROR:

CLEAN UP THE MESS

Если программа обработки ошибок нетривиальна и ошибки могут возникать в нескольких местах, то такая организация оказыва- ется удобной. Метка имеет такую же форму, что и имя перемен- ной, и за ней всегда следует двоеточие. Метка может быть приписана к любому оператору той же функции, в которой нахо- дится оператор GOTO.

В качестве другого примера рассмотрим задачу нахождения первого отрицательного элемента в двумерном массиве. (Много- мерные массивы рассматриваются в главе 5). Вот одна из воз- можностей:

FOR (I = 0; I < N; I++)

FOR (J = 0; J < M; J++)

IF (V[I][J] < 0)

GOTO FOUND;

/* DIDN'T FIND */

... FOUND:

/* FOUND ONE AT POSITION I, J */

...

Программа, использующая оператор GOTO, всегда может быть написана без него, хотя, возможно, за счет повторения неко- торых проверок и введения дополнительных переменных. Напри- мер, программа поиска в массиве примет вид:

FOUND = 0; FOR (I = 0; I < N && !FOUND; I++)

FOR (J = 0; J < M && !FOUND; J++)

FOUND = V[I][J] < 0; IF (FOUND)

/* IT WAS AT I-1, J-1 */

... ELSE

/* NOT FOUND */

...

Хотя мы не являемся в этом вопросе догматиками, нам все же кажется, что если и нужно использовать оператор GOTO, то весьма умеренно.

* 4. Функции и структура программ *

Функции разбивают большие вычислительные задачи на ма- ленькие подзадачи и позволяют использовать в работе то, что уже сделано другими, а не начинать каждый раз с пустого мес- та. Соответствующие функции часто могут скрывать в себе де- тали проводимых в разных частях программы операций, знать которые нет необходимости, проясняя тем самым всю программу, как целое, и облегчая мучения при внесении изменений.

Язык "C" разрабатывался со стремлением сделать функции эффективными и удобными для использования; "C"-программы обычно состоят из большого числа маленьких функций, а не из нескольких больших. Программа может размещаться в одном или нескольких исходных файлах любым удобным образом; исходные файлы могут компилироваться отдельно и загружаться вместе наряду со скомпилированными ранее функциями из библиотек. Мы здесь не будем вдаваться в детали этого процесса, поскольку они зависят от используемой системы.

Большинство программистов хорошо знакомы с "библиотечны- ми" функциями для ввода и вывода /GETCHAR , PUTCHAR/ и для численных расчетов /SIN, COS, SQRT/. В этой главе мы сообщим больше о написании новых функций.

4.1. Основные сведения

Для начала давайте разработаем и составим программу пе- чати каждой строки ввода, которая содержит определенную ком- бинацию символов. /Это - специальный случай утилиты GREP системы "UNIX"/. Например, при поиске комбинации "THE" в на- боре строк

NOW IS THE TIME

FOR ALL GOOD

MEN TO COME TO THE AID

OF THEIR PARTY в качестве выхода получим

NOW IS THE TIME

MEN TO COME TO THE AID

OF THEIR PARTY

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

WHILE (имеется еще строка)

IF (строка содержит нужную комбинацию)

вывод этой строки

Конечно, возможно запрограммировать все действия в виде одной основной процедуры, но лучше использовать естественную структуру задачи и представить каждую часть в виде отдельной функции. С тремя маленькими кусками легче иметь дело, чем с одним большим, потому что отдельные не относящиеся к сущест- ву дела детали можно включить в функции и уменьшить возмож- ность нежелательных взаимодействий. Кроме того, эти куски могут оказаться полезными сами по себе.

"Пока имеется еще строка" - это GETLINE, функция, кото- рую мы запрограммировали в главе 1, а "вывод этой строки" - это функция PRINTF, которую уже кто-то подготовил для нас. Это значит, что нам осталось только написать процедуру для определения, содержит ли строка данную комбинацию символов или нет. Мы можем решить эту проблему, позаимствовав разра- ботку из PL/1: функция INDEX(S,т) возвращает позицию, или индекс, строки S, где начинается строка T, и -1, если S не содержит т . В качестве начальной позиции мы используем 0, а не 1, потому что в языке "C" массивы начинаются с позиции нуль. Когда нам в дальнейшем понадобится проверять на совпа- дение более сложные конструкции, нам придется заменить толь- ко функцию INDEX; остальная часть программы останется той же самой.

После того, как мы потратили столько усилий на разработ- ку, написание программы в деталях не представляет затрудне- ний. ниже приводится целиком вся программа, так что вы може- те видеть, как соединяются вместе отдельные части. Комбина- ция символов, по которой производится поиск, выступает пока в качестве символьной строки в аргументе функции INDEX, что не является самым общим механизмом. Мы скоро вернемся к об- суждению вопроса об инициализации символьных массивов и в главе 5 покажем, как сделать комбинацию символов параметром, которому присваивается значение в ходе выполнения программы. Программа также содержит новый вариант функции GETLINE; вам может оказаться полезным сравнить его с вариантом из главы 1.

#DEFINE MAXLINE 1000 MAIN() /* FIND ALL LINES MATCHING A PATTERN */ {

CHAR LINE[MAXLINE];

WHILE (GETLINE(LINE, MAXLINE) > 0)

IF (INDEX(LINE, "THE") >= 0)

PRINTF("%S", LINE);

} GETLINE(S, LIM) /* GET LINE INTO S, RETURN LENGTH *

CHAR S[];

INT LIM;

{

INT C, I;

I = 0; WHILE(--LIM>0 && (C=GETCHAR()) != EOF && C != '\N')

S[I++] = C;

IF (C == '\N')

S[I++] = C;

S[I] = '\0';

RETURN(I);

}

INDEX(S,T) /* RETURN INDEX OF T IN S,-1 IF NONE */

CHAR S[], T[];

{

INT I, J, K;

FOR (I = 0; S[I] != '\0'; I++) {

FOR(J=I, K=0; T[K] !='\0' && S[J] == T[K]; J++; K++)

;

IF (T[K] == '\0')

RETURN(I);

}

RETURN(-1);

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

123456789101112131415161718192021222324252627282930313233


 
Page generation 0.004 seconds
ввг-нг-ls, короб.