Реферат: Язык С
LOWER, преобразующая данную
прописную букву в строчную. Если
выступающий в качестве аргумента
символ не является пропис-
ной буквой, то LOWER возвращает его
неизменным. Приводимая
ниже программа справедлива только
для набора символов ASCII.
LOWER© /* CONVERT C TO LOWER CASE;
ASCII ONLY */
INT C;
{
IF ( C >= 'A' && C
<= 'Z' )
RETURN( C + '@' - 'A');
ELSE /*@
Записано вместо 'A' строчного*/
RETURN©;
}
Эта функция правильно работает при
коде ASCII, потому что
численные значения, соответствующие
в этом коде прописным и
строчным буквам, отличаются на
постоянную величину, а каждый
алфавит является сплошным - между а
и Z нет ничего, кроме
букв. Это последнее замечание для
набора символов EBCDIC
систем IBM 360/370 оказывается
несправедливым, в силу чего
эта программа на таких системах
работает неправильно - она
преобразует не только буквы.
При преобразовании символьных
переменных в целые возни-
кает один тонкий момент. Дело в
том, что сам язык не указы-
вает, должны ли переменным типа
CHAR соответствовать числен-
ные значения со знаком или без
знака. Может ли при преобра-
зовании CHAR в INT получиться
отрицательное целое? К сожале-
нию, ответ на этот вопрос меняется
от машины к машине, отра-
жая расхождения в их архитектуре.
На некоторых машинах
(PDP-11, например) переменная типа
CHAR, крайний левый бит
которой содержит 1, преобразуется в
отрицательное целое
(“знаковое расширение”). На других
машинах такое преобразо-
вание сопровождается добавлением
нулей с левого края, в ре-
зультате чего всегда получается
положительное число.
·
47 -
Определение языка “C”
гарантирует, что любой символ из
стандартного набора символов машины
никогда не даст отрица-
тельного числа, так что эти символы
можно свободно использо-
вать в выражениях как положительные
величины. Но произволь-
ные комбинации двоичных знаков,
хранящиеся как символьные
переменные на некоторых машинах,
могут дать отрицательные
значения, а на других
положительные.
Наиболее типичным примером
возникновения такой ситуации
является сучай, когда значение 1
используется в качестве
EOF. Рассмотрим программу
CHAR C;
C = GETCHAR();
IF ( C == EOF )
...
На машине, которая не
осуществляет знакового расширения,
переменная 'с' всегда положительна,
поскольку она описана
как CHAR, а так как EOF отрицательно,
то условие никогда не
выполняется. Чтобы избежать такой
ситуации, мы всегда пре-
дусмотрительно использовали 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 явля-
ется константным выражением и,
следовательно, обрабатывается
во время компиляции.
Чтобы проиллюстрировать
использование некоторых операций
с битами, рассмотрим функцию
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));
}
·
53 -
Операция 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 - выражения, то
·
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акая операция присваивания
облегчает понимание программы,
так как читатель не должен
скрупулезно проверять, являются
ли два длинных выражения
действительно одинаковыми, или за-
думываться, почему они не
совпадают. Такая операция присваи-
вания может даже помочь компилятору
получить более эффектив-
Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
|