дусмотрительно использовали INT вместо CHAR для любой пере-
менной, получающей значение от GETCHAR.
Основная же причина использования INT вместо CHAR не
связана с каким-либо вопросом о возможном знаковом расшире-
нии. просто функция GETCHAR должна передавать все возможные
символы (чтобы ее можно было использовать для произвольного
ввода) и, кроме того, отличающееся значение EOF. Следова-
тельно значение EOF не может быть представлено как CHAR, а
должно храниться как INT.
Другой полезной формой автоматического преобразования
типов является то, что выражения отношения, подобные I>J, и
логические выражения, связанные операциями && и \!\!, по оп-
ределению имеют значение 1, если они истинны, и 0, если они
ложны. Таким образом, присваивание
ISDIGIT = C >= '0' && C <= '9';
полагает ISDIGIT равным 1, если с - цифра, и равным 0 в про-
тивном случае. (В проверочной части операторов IF, WHILE,
FOR и т.д. “Истинно” просто означает “не нуль”).
Неявные арифметические преобразования работают в основ-
ном, как и ожидается. В общих чертах, если операция типа +
или *, которая связывает два операнда (бинарная операция),
имеет операнды разных типов, то перед выполнением операции
“низший” тип преобразуется к “высшему” и получается резуль-
тат “высшего” типа. Более точно, к каждой арифметической
операции применяется следующая последовательность правил
преобразования.
· Типы CHAR и SHORT преобразуются в INT, а FLOAT в
DOUBLE.
·
48 -
· Затем, если один из операндов имеет тип DOUBLE, то
другой преобразуется в DOUBLE, и результат имеет тип DOUBLE.
· В противном случае, если один из операндов имеет тип LONG, то другой преобразуется в LONG, и результат имеет тип LONG.
· В противном случае, если один из операндов имеет тип UNSIGNED, то другой преобразуется в UNSIGNED и результат имеет тип UNSIGNED.
· В противном случае операнды должны быть типа INT, и
результат имеет тип INT.
Подчеркнем, что все переменные типа FLOAT в выражениях пре-
образуются в DOUBLE; в “C” вся плавающая арифметика выполня-
ется с двойной точностью.
Преобразования возникают и при присваиваниях; значение
правой части преобразуется к типу левой, который и является
типом результата. Символьные переменные преобразуются в це-
лые либо со знаковым расширением ,либо без него, как описано
выше. Обратное преобразование INT в CHAR ведет себя хорошо -
лишние биты высокого порядка просто отбрасываются. Таким об-
разом
INT I;
CHAR C;
I = C;
C = I;
значение 'с' не изменяется. Это верно независимо от того,
вовлекается ли знаковое расширение или нет.
Если х типа FLOAT, а I типа INT, то как
х = I;
так и
I = х;
приводят к преобразованиям; при этом FLOAT преобразуется в
INT отбрасыванием дробной части. Тип DOUBLE преобразуется во
FLOAT округлением. Длинные целые преобразуются в более ко-
роткие целые и в переменные типа CHAR посредством отбрасыва-
ния лишних битов высокого порядка.
Так как аргумент функции является выражением, то при пе-
редаче функциям аргументов также происходит преобразование
типов: в частности, CHAR и SHORT становятся INT, а FLOAT
становится DOUBLE. Именно поэтому мы описывали аргументы
функций как INT и DOUBLE даже тогда, когда обращались к ним
с переменными типа CHAR и FLOAT.
Наконец, в любом выражении может быть осуществлено
(“принуждено”) явное преобразование типа с помощью конструк-
ции, называемой перевод (CAST). В этой конструкции, имеющей
вид
(имя типа) выражение
· 49 -
Выражение преобразуется к указанному типу по правилам
преобразования, изложенным выше. Фактически точный смысл
операции перевода можно описать следующим образом: выражение
как бы присваивается некоторой переменной указанного типа,
которая затем используется вместо всей конструкции. Напри-
мер, библиотечная процедура SQRT ожидает аргумента типа
DOUBLE и выдаст бессмысленный ответ, если к ней по небреж-
ности обратятся с чем-нибудь иным. таким образом, если N -
целое, то выражение
SQRT((DOUBLE) N)
до передачи аргумента функции SQRT преобразует N к типу
DOUBLE. (Отметим, что операция перевод преобразует значение
N в надлежащий тип; фактическое содержание переменной N при
этом не изменяется). Операция перевода имрация перевода име-
ет тот же уровень старшинства, что и другие унарные опера-
ции, как указывается в таблице в конце этой главы.
Упражнение 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;
·
50 -
полагает х равным 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;
· 51 -
В качестве третьего примера рассмотрим функцию
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
· 52 -
передает в 'с' семь младших битов 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 явля-
ется константным выражением и, следовательно, обрабатывается