рефераты

рефераты

 
 
рефераты рефераты

Меню

Реферат: Язык С рефераты

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