требуется на одну ячейку памяти больше, чем число заключен-
ных в кавычки символов. Следующая функция STRLEN(S) вычисля-
ет длину символьной строки S не считая конечный символ \0.
STRLEN(S) /* RETURN LENGTH OF S */
CHAR S[];
{
INT I;
I = 0;
WHILE (S[I] != '\0')
++I;
RETURN(I);
}
Будьте внимательны и не путайте символьную константу со
строкой, содержащей один символ: 'X' - это не то же самое,
что “X”. Первое - это отдельный символ, использованный с
целью получения численного значения, соответствующего букве
х в машинном наборе символов. Второе - символьная строка,
состоящая из одного символа (буква х) и \0.
· 43 -
2.4. Описания
Все переменные должны быть описаны до их использования,
хотя некоторые описания делаются неявно, по контексту. Опи-
сание состоит из спецификатора типа и следующего за ним
списка переменных, имеющих этот тип, как, например,
INT LOWER, UPPER, STEP;
CHAR C, LINE[1000];
Переменные можно распределять по описаниям любым обра-
зом; приведенные выше списки можно с тем же успехом записать
в виде
INT LOWER;
INT UPPER;
INT STEP;
CHAR C;
CHAR LINE[1000];
Такая форма занимает больше места, но она удобна для до-бавления комментария к каждому описанию и для последующих модификаций.
Переменным могут быть присвоены начальные значения внут-
ри их описания, хотя здесь имеются некоторые ограничения.
Если за именем переменной следуют знак равенства и констан-
та, то эта константа служит в качестве инициализатора, как,
например, в
CHAR BACKSLASH = '\';
INT I = 0;
FLOAT EPS = 1.0E-5;
Если рассматриваемая переменная является внешней или
статической, то инициализация проводится только один раз,
согласно концепции до начала выполнения программы. Инициали-
зируемым явно автоматическим переменным начальные значения
присваиваются при каждом обращении к функции, в которой они
описаны. Автоматические переменные, не инициализируемые яв-
но, имеют неопределенные значения, (т.е. мусор). Внешние и
статические переменные по умолчанию инициализируются нулем,
но, тем не менее, их явная инициализация является признаком
хорошего стиля.
Мы продолжим обсуждение вопросов инициализации, когда
будем описывать новые типы данных.
2.5. Арифметические операции.
Бинарными арифметическими операциями являются +, -, *, /
и операция деления по модулю %. Имеется унарная операция -,
но не существует унарной операции +.
·
44 -
При делении целых дробная часть отбрасывается. Выражение
X % Y
дает остаток от деления X на Y и, следовательно, равно нулю,
когда х делится на Y точно. Например, год является високос-
ным, если он делится на 4, но не делится на 100, исключая
то, что делящиеся на 400 годы тоже являются високосными. По-
этому
IF(YEAR % 4 == 0 && YEAR % 100 != 0 \!\! YEAR % 400 == 0)
год високосный
ELSE
год невисокосный
Операцию % нельзя использовать с типами FLOAT или
DOUBLE.
Операции + и - имеют одинаковое старшинство, которое
младше одинакового уровня старшинства операций *, / и %, ко-
торые в свою очередь младше унарного минуса. Арифметические
операции группируются слева направо. (Сведения о старшинстве
и ассоциативности всех операций собраны в таблице в конце
этой главы). Порядок выполнения ассоциативных и коммутатив-
ных операций типа + и - не фиксируется; компилятор может пе-
регруппировывать даже заключенные в круглые скобки выраже-
ния, связанные такими операциями. таким образом, а+(B+C) мо-
жет быть вычислено как (A+B)+C. Это редко приводит к како-
му-либо расхождению, но если необходимо обеспечить строго
определенный порядок, то нужно использовать явные промежу-
точные переменные.
Действия, предпринимаемые при переполнении и антипере-
полнении (т.е. При получении слишком маленького по абсолют-
ной величине числа), зависят от используемой машины.
2.6. Операции отношения и логические операции
Операциями отношения являются
=> > =< <
все они имеют одинаковое старшинство. Непосредственно за ни-
ми по уровню старшинства следуют операции равенства и нера-
венства:
== !=
которые тоже имеют одинаковое старшинство. операции отноше-
ния младше арифметических операций, так что выражения типа
I<LIM-1 понимаются как I<(LIM-1), как и предполагается.
Логические связки && и \!\! более интересны. Выражения,
связанные операциями && и \!\!, вычисляются слева направо,
причем их рассмотрение прекращается сразу же как только ста-
новится ясно, будет ли результат истиной или ложью. учет
этих свойств очень существенен для написания правильно рабо-
тающих программ. Рассмотрим, например, оператор цикла в счи-
тывающей строку функции GETLINE, которую мы написали в главе
1.
· 45 -
FOR(I=0;I<LIM-1 && (C=GETCHAR()) != '\N' && C != EOF; ++I)
S[I]=C;
Ясно, что перед считыванием нового символа необходимо
проверить, имеется ли еще место в массиве S, так что условие
I<LIM-1 должно проверяться первым. И если это условие не вы-
полняется, мы не должны считывать следующий символ.
Так же неудачным было бы сравнение 'C' с EOF до обраще-
ния к функции GETCHAR : прежде чем проверять символ, его
нужно считать.
Старшинство операции && выше, чем у \!\!, и обе они
младше операций отношения и равенства. Поэтому такие выраже-
ния, как
I<LIM-1 && (C = GETCHAR()) != '\N' && C != EOF
не нуждаются в дополнительных круглых скобках. Но так как
операция != старше операции присваивания, то для достижения
правильного результата в выражении
(C = GETCHAR()) != '\N'
кобки необходимы.
Унарная операция отрицания ! Преобразует ненулевой или
истинный операнд в 0, а нулевой или ложный операнд в 1.
Обычное использование операции ! Заключается в записи
IF( ! INWORD )
Вместо
IF( INWORD == 0 )
Tрудно сказать, какая форма лучше. Конструкции типа ! INWORD
Читаются довольно удобно (“если не в слове”). Но в более
сложных случаях они могут оказаться трудными для понимания.
Упражнение 2-1.
Напишите оператор цикла, эквивалентный приведенному выше
оператору FOR, не используя операции &&.
2.7. Преобразование типов
Если в выражениях встречаются операнды различных типов,
то они преобразуются к общему типу в соответствии с неболь-
шим набором правил. В общем, автоматически производятся
только преобразования, имеющие смысл, такие как, например,
преобразование целого в плавающее в выражениях типа F+I. Вы-
ражения же, лишенные смысла, такие как использование пере-
менной типа FLOAT в качестве индекса, запрещены.
Во-первых, типы CHAR и INT могут свободно смешиваться в
арифметических выражениях: каждая переменная типа CHAR авто-
матически преобразуется в INT. Это обеспечивает значительную
гибкость при проведении определенных преобразований симво-
лов. Примером может служить функция ATOI, которая ставит в
соответствие строке цифр ее численный эквивалент.
· 46 -
ATOI(S) /* CONVERT S TO INTEGER */
CHAR S[];
{
INT I, N;
N = 0;
FOR ( I = 0; S[I]>='0' && S[I]<='9'; ++I)
N = 10 * N + S[I] - '0';
RETURN(N);
}
KAK Уже обсуждалось в главе 1, выражение
S[I] - '0'
имеет численное значение находящегося в S[I] символа, потому
что значение символов '0', '1' и т.д. образуют возрастающую
последовательность расположенных подряд целых положительных
чисел.
Другой пример преобразования CHAR в INT дает функция
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 отрицательно, то условие никогда не
выполняется. Чтобы избежать такой ситуации, мы всегда пре-