Упражнение 2-2
---------------
Составьте программу для функции HTOI(S), которая преоб-
разует строку шестнадцатеричных цифр в эквивалентное ей це-
лое значение. При этом допустимыми цифрами являются цифры от
1 до 9 и буквы от а до F.
2.8. Операции увеличения и уменьшения
В языке "C" предусмотрены две необычные операции для
увеличения и уменьшения значений переменных. Операция увели-
чения ++ добавляет 1 к своему операнду, а операция уменьше-
ния -- вычитает 1. Мы часто использовали операцию ++ для
увеличения переменных, как, например, в
IF(C == '\N')
++I;
Необычный аспект заключается в том, что ++ и -- можно
использовать либо как префиксные операции (перед переменной,
как в ++N), либо как постфиксные (после переменной: N++).
Эффект в обоих случаях состоит в увеличении N. Но выражение
++N увеличивает переменную N до использования ее значения, в
то время как N++ увеличивает переменную N после того, как ее
значение было использовано. Это означает, что в контексте,
где используется значение переменной, а не только эффект
увеличения, использование ++N и N++ приводит к разным ре-
зультатам. Если N = 5, то
х = N++;
устанавливает х равным 5, а
х = ++N;
полагает х равным 6. В обоих случаях N становится равным 6.
Операции увеличения и уменьшения можно применять только к
переменным; выражения типа х=(I+J)++ являются незаконными.
В случаях, где нужен только эффект увеличения, а само
значение не используется, как, например, в
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<Упражнение 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, которая переводит
прописные буквы в строчные, используя вместо конструкции