Смекни!
smekni.com

во время компиляции.

Чтобы проиллюстрировать использование некоторых операций

с битами, рассмотрим функцию 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)) & &bsol;^(&bsol;^0 << N));

}

· 53 -

Операция X >> (P+1-N) сдвигает желаемое поле в правый конец

слова. Описание аргумента X как UNSIGNED гарантирует, что

при сдвиге вправо освобождающиеся биты будут заполняться ну-

лями, а не содержимым знакового бита, независимо от того, на

какой машине пропускается программа. Все биты константного

выражения &bsol;^0 равны 1; сдвиг его на N позиций влево с по-

мощью операции &bsol;^0<<N создает маску с нулями в N крайних

правых битах и единицами в остальных; дополнение &bsol;^ создает

маску с единицами в 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

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

Большинству бинарных операций (операций подобных +, ко-

торые имеют левый и правый операнд) соответствует операция

присваивания вида оп=, где оп - одна из операций

+ - * / % << >> & &bsol;^ &bsol;!

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

· 54 -

е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акая операция присваивания облегчает понимание программы,

так как читатель не должен скрупулезно проверять, являются

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

думываться, почему они не совпадают. Такая операция присваи-

вания может даже помочь компилятору получить более эффектив-

ную программу.

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

имеет некоторое значение и может входить в выражения; самый

типичный пример

· 55 -

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 или

нет.

· 56 -

Так как уровень старшинства операции ?: очень низок,

прямо над присваиванием, то первое выражение в условном вы-

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

же рекомендуем это делать, так как скобки делают условную

часть выражения более заметной.

Использование условных выражений часто приводит к корот-

ким программам. Например, следующий ниже оператор цикла пе-

чатает N элементов массива, по 10 в строке, разделяя каждый

столбец одним пробелом и заканчивая каждую строку (включая

последнюю) одним символом перевода строки.

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

PRINTF(“%6D%C”,A[I],(I%10==9 &bsol;!&bsol;! I==N-1) ? '&bsol;N' : ' ')

Символ перевода строки записывается после каждого десятого

элемента и после N-го элемента. За всеми остальными элемен-

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

реным, было бы поучительным попытаться записать это, не ис-

пользуя условного выражения.

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

Перепишите программу для функции LOWER, которая переводит

прописные буквы в строчные, используя вместо конструкции

IF-ELSE условное выражение.

2.12. Старшинство и порядок вычисления.

В приводимой ниже таблице сведены правила старшинства и ас-

социативности всех операций, включая и те, которые мы еще не

обсуждали. Операции, расположенные в одной строке, имеют

один и тот же уровень старшинства; строки расположены в по-

рядке убывания старшинства. Так, например, операции *, / и %

имеют одинаковый уровень старшинства, который выше, чем уро-

вень операций + и -.

OPERATOR ASSOCIATIVITY

() [] -> . LEFT TO RIGHT

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

* / % LEFT TO RIGHT

+ - LEFT TO RIGHT

<< >> LEFT TO RIGHT

< <= > >= LEFT TO RIGHT

· 57 -

== != LEFT TO RIGHT

& LEFT TO RIGHT

^ LEFT TO RIGHT

&bsol;! LEFT TO RIGHT

&& LEFT TO RIGHT

&bsol;!&bsol;! 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&bsol;N”,++N,POWER(2,N));

·
58 -

может давать (и действительно дает) на разных машинах разные

результаты в зависимости от того, увеличивается ли N до или

после обращения к функции POWER. Правильным решением, конеч-

но, является запись

++N;

PRINTF(“%D %D&bsol;N”,N,POWER(2,N));

Обращения к функциям, вложенные операции присваивания,

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

“побочным эффектам” - некоторые переменные изменяются как

побочный результат вычисления выражений. В любом выражении,

в котором возникают побочные эффекты, могут существовать

очень тонкие зависимости от порядка, в котором определяются

входящие в него переменные. примером типичной неудачной си-

туации является оператор

A[I] = I++;

Возникает вопрос, старое или новое значение I служит в ка-

честве индекса. Компилятор может поступать разными способами

и в зависимости от своей интерпретации выдавать разные ре-

зультаты. Тот случай, когда происходят побочные эффекты

(присваивание фактическим переменным), - оставляется на ус-

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

сит от архитектуры машины.

Из этих рассуждений вытекает такая мораль: написание

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

методом программирования на любом языке. Конечно, необходимо