RuLibrary.com

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

 
 


 

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


В случаях, где нужен только эффект увеличения, а само значение не используется, как, например, в

IF ( C == '\N' )

NL++;

выбор префиксной или постфиксной операции является делом вкуса. но встречаются ситуации, где нужно использовать имен- но ту или другую операцию. Рассмотрим, например, функцию SQUEEZE(S,C), которая удаляет символ 'с' из строки S, каждый раз, как он встречается.

SQUEEZE(S,C) /* DELETE ALL C FROM S */ CHAR S[]; INT C; {

INT I, J;

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

IF ( S[I] != C )

S[J++] = S[I];

S[J] = '\0'; }

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

IF ( S[I] != C ) {

S[J] = S[I];

J++;

}

Другой пример подобной конструкции дает функция GETLINE, которую мы запрограммировали в главе 1, где можно заменить

IF ( C == '\N' ) {

S[I] = C;

++I; }

более компактной записью

IF ( C == '\N' )

S[I++] = C;

В качестве третьего примера рассмотрим функцию STRCAT(S,T), которая приписывает строку т в конец строки S, образуя конкатенацию строк S и т. При этом предполагается, что в S достаточно места для хранения полученной комбинации.

STRCAT(S,T) /* CONCATENATE T TO END OF S */

CHAR S[], T[]; /* S MUST BE BIG ENOUGH */

{

INT I, J;

I = J = 0;

WHILE (S[I] != '\0') / *FIND END OF S */

I++;

WHILE((S[I++] = T[J++]) != '\0') /*COPY T*/

;

}

Tак как из T в S копируется каждый символ, то для подготовки к следующему прохождению цикла постфиксная операция ++ при- меняется к обеим переменным I и J.

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

Напишите другой вариант функции SQUEEZE(S1,S2), который удаляет из строки S1 каждый символ, совпадающий с каким-либо символом строки S2.

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

Напишите программу для функции ANY(S1,S2), которая нахо- дит место первого появления в строке S1 какого-либо символа из строки S2 и, если строка S1 не содержит символов строки S2, возвращает значение -1.

2.9. Побитовые логические операции

В языке предусмотрен ряд операций для работы с битами; эти операции нельзя применять к переменным типа FLOAT или DOUBLE.

& Побитовое AND

\! Побитовое включающее OR

^ побитовое исключающее OR

<< сдвиг влево

>> сдвиг вправо

\^ дополнение (унарная операция)

"\" иммитирует вертикальную черту.

Побитовая операция AND часто используется для маскирования некоторого множества битов; например, оператор

C = N & 0177 передает в 'с' семь младших битов N , полагая остальные рав- ными нулю. Операция 'э' побитового OR используется для вклю- чения битов:

C = X э MASK

устанавливает на единицу те биты в х , которые равны единице в MASK.

Следует быть внимательным и отличать побитовые операции & и 'э' от логических связок && и \!\! , Которые подразуме- вают вычисление значения истинности слева направо. Например, если х=1, а Y=2, то значение х&Y равно нулю , в то время как значение X&&Y равно единице./почему?/

Операции сдвига << и >> осуществляют соответственно сдвиг влево и вправо своего левого операнда на число битовых позиций, задаваемых правым операндом. Таким образом , х<<2 сдвигает х влево на две позиции, заполняя освобождающиеся биты нулями, что эквивалентно умножению на 4. Сдвиг вправо величины без знака заполняет освобождающиеся биты на некото- рых машинах, таких как PDP-11, заполняются содержанием зна- кового бита /"арифметический сдвиг"/, а на других - нулем /"логический сдвиг"/.

Унарная операция \^ дает дополнение к целому; это озна- чает , что каждый бит со значением 1 получает значение 0 и наоборот. Эта операция обычно оказывается полезной в выраже- ниях типа

X & \^077

где последние шесть битов х маскируются нулем. Подчеркнем, что выражение X&\^077 не зависит от длины слова и поэтому предпочтительнее, чем, например, X&0177700, где предполага- ется, что х занимает 16 битов. Такая переносимая форма не требует никаких дополнительных затрат, поскольку \^077 явля- ется константным выражением и, следовательно, обрабатывается во время компиляции.

Чтобы проиллюстрировать использование некоторых операций с битами, рассмотрим функцию GETBITS(X,P,N), которая возвра- щает /сдвинутыми к правому краю/ начинающиеся с позиции р поле переменной х длиной N битов. мы предполагаем , что крайний правый бит имеет номер 0, и что N и р - разумно за- данные положительные числа. например, GETBITS(х,4,3) возвра- щает сдвинутыми к правому краю биты, занимающие позиции 4,3 и 2.

GETBITS(X,P,N) /* GET N BITS FROM POSITION P */

UNSIGNED X, P, N;

{

RETURN((X >> (P+1-N)) & \^(\^0 << N));

}

Операция X >> (P+1-N) сдвигает желаемое поле в правый конец слова. Описание аргумента X как UNSIGNED гарантирует, что при сдвиге вправо освобождающиеся биты будут заполняться ну- лями, а не содержимым знакового бита, независимо от того, на какой машине пропускается программа. Все биты константного выражения \^0 равны 1; сдвиг его на N позиций влево с по- мощью операции \^0<<N создает маску с нулями в N крайних правых битах и единицами в остальных; дополнение \^ создает маску с единицами в N крайних правых битах.

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

Переделайте GETBITS таким образом, чтобы биты отсчитыва- лись слева направо.

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

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

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

Напишите программу для функции RIGHTROT(N,B), сдвигающей циклически целое N вправо на B битовых позиций.

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

Напишите программу для функции INVERT(X,P,N), которая инвертирует (т.е. Заменяет 1 на 0 и наоборот) N битов X, на- чинающихся с позиции P, оставляя другие биты неизмененными.

2.10. Операции и выражения присваивания

Такие выражения, как

I = I + 2

в которых левая часть повторяется в правой части могут быть записаны в сжатой форме

I += 2

используя операцию присваивания вида +=.

Большинству бинарных операций (операций подобных +, ко- торые имеют левый и правый операнд) соответствует операция присваивания вида оп=, где оп - одна из операций

+ - * / % << >> & \^ \!

Если е1 и е2 - выражения, то е1 оп= е2

эквивалентно

е1 = (е1) оп (е2)

за исключением того, что выражение е1 вычисляется только один раз. Обратите внимание на круглые скобки вокруг е2:

X *= Y + 1

то

X = X * (Y + 1)

не

X = X * Y + 1

В качестве примера приведем функцию BITCOUNT, которая подсчитывает число равных 1 битов у целого аргумента.

BITCOUNT(N) /* COUNT 1 BITS IN N */ UNSIGNED N; ( INT B; FOR (B = 0; N != 0; N >>= 1)

IF (N & 01)

B++; RETURN(B); )

Не говоря уже о краткости, такие операторы приваивания имеют то преимущество, что они лучше соответствуют образу человеческого мышления. Мы говорим: "прибавить 2 к I" или "увеличить I на 2", но не "взять I, прибавить 2 и поместить результат опять в I". Итак, I += 2. Кроме того, в громоздких выражениях, подобных

YYVAL[YYPV[P3+P4] + YYPV[P1+P2]] += 2

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

Мы уже использовали тот факт, что операция присваивания имеет некоторое значение и может входить в выражения; самый типичный пример

WHILE ((C = GETCHAR()) != EOF)

присваивания, использующие другие операции присваивания (+=, -= и т.д.) также могут входить в выражения, хотя это случа- ется реже.

Типом выражения присваивания является тип его левого операнда.

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

В двоичной системе счисления операция X&(X-1) обнуляет самый правый равный 1 бит переменной X.(почему?) используйте это замечание для написания более быстрой версии функции BITCOUNT.

2.11. Условные выражения

Операторы

IF (A > B)

Z = A;

ELSE

Z = B;

конечно вычисляют в Z максимум из а и в. Условное выражение, записанное с помощью тернарной операции "?:", предоставляет другую возможность для записи этой и аналогичных конструк- ций. В выражении

е1 ? Е2 : е3

сначала вычисляется выражение е1. Если оно отлично от нуля (истинно), то вычисляется выражение е2, которое и становится значением условного выражения. В противном случае вычисляет- ся е3, и оно становится значением условного выражения. Каж- дый раз вычисляется только одно из выражения е2 и е3. Таким образом, чтобы положить Z равным максимуму из а и в, можно написать

Z = (A > B) ? A : B; /* Z = MAX(A,B) */

Следует подчеркнуть, что условное выражение действитель- но является выражением и может использоваться точно так же, как любое другое выражение. Если е2 и е3 имеют разные типы, то тип результата определяется по правилам преобразования, рассмотренным ранее в этой главе. например, если F имеет тип FLOAT, а N - тип INT, то выражение

(N > 0) ? F : N

Имеет тип DOUBLE независимо от того, положительно ли N или нет.

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

Использование условных выражений часто приводит к корот- ким программам. Например, следующий ниже оператор цикла пе- чатает N элементов массива, по 10 в строке, разделяя каждый столбец одним пробелом и заканчивая каждую строку (включая последнюю) одним символом перевода строки.

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

PRINTF("%6D%C",A[I],(I%10==9 \!\! I==N-1) ? '\N' : ' ')

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

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

Перепишите программу для функции LOWER, которая переводит прописные буквы в строчные, используя вместо конструкции IF-ELSE условное выражение.
2.12. Старшинство и порядок вычисления

В приводимой ниже таблице сведены правила старшинства и ас- социативности всех операций, включая и те, которые мы еще не обсуждали. Операции, расположенные в одной строке, имеют один и тот же уровень старшинства; строки расположены в по- рядке убывания старшинства. Так, например, операции *, / и % имеют одинаковый уровень старшинства, который выше, чем уро- вень операций + и -.

OPERATOR ASSOCIATIVITY

() [] -> . LEFT TO RIGHT

! \^ ++ -- - (TYPE) * & SIZEOF RIGHT TO LEFT

* / % LEFT TO RIGHT

+ - LEFT TO RIGHT

<< >> LEFT TO RIGHT

< <= > >= LEFT TO RIGHT

== != LEFT TO RIGHT

& LEFT TO RIGHT

^ LEFT TO RIGHT

\! LEFT TO RIGHT

&& LEFT TO RIGHT

\!\! LEFT TO RIGHT

?: RIGHT TO LEFT

= += -= ETC. RIGHT TO LEFT

, (CHAPTER 3) LEFT TO RIGHT

Операции -> и . Используются для доступа к элементам струк- тур; они будут описаны в главе 6 вместе с SIZEOF (размер объекта). В главе 5 обсуждаются операции * (косвенная адре- сация) и & (адрес). Отметим, что уровень старшинства побитовых логических опера- ций &, ^ и э ниже уровня операций == и !=. Это приводит к тому, что осуществляющие побитовую проверку выражения, по- добные

IF ((X & MASK) == 0) ...

Для получения правильных результатов должны заключаться в круглые скобки.

Как уже отмечалось ранее, выражения, в которые входит одна из ассоциативных и коммутативных операций (*, +, &, ^, э), могут перегруппировываться, даже если они заключены в круглые скобки. В большинстве случаев это не приводит к ка- ким бы то ни было расхождениям; в ситуациях, где такие рас- хождения все же возможны, для обеспечения нужного порядка вычислений можно использовать явные промежуточные перемен- ные.

В языке "C", как и в большинстве языков, не фиксируется порядок вычисления операндов в операторе. Например в опера- торе вида

X = F() + G();

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

Подобным же образом не фиксируется порядок вычисления аргументов функции, так что оператор

PRINTF("%D %D\N",++N,POWER(2,N));

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

++N; PRINTF("%D %D\N",N,POWER(2,N));

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

A[I] = I++;

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

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

123456789101112131415161718192021222324252627282930313233


 
Page generation 0.003 seconds