Если программа находится в нескольких исходных файлах, и
некоторая переменная определена, скажем в файле 1, а исполь-
зуется в файле 2, то чтобы связать эти два вхождения пере-
менной, необходимо в файле 2 использовать описание EXTERN.
Этот вопрос подробно обсуждается в главе 4.
Вы должно быть заметили, что мы в этом разделе при ссыл-
ке на внешние переменные очень аккуратно используем слова
описание и определение. "Определение" относится к тому мес-
ту, где переменная фактически заводится и ей выделяется па-
мять; "описание" относится к тем местам, где указывается
природа переменной, но никакой памяти не отводится.
Между прочим, существует тенденция объявлять все, что ни
попадется, внешними переменными, поскольку кажется, что это
упрощает связи, - списки аргументов становятся короче и пе-
ременные всегда присутствуют, когда бы вам они ни понадоби-
лись. Но внешние переменные присутствуют и тогда, когда вы в
них не нуждаетесь. Такой стиль программирования чреват опас-
ностью, так как он приводит к программам, связи данных внут-
ри которых не вполне очевидны. Переменные при этом могут из-
меняться неожиданным и даже неумышленным образом, а програм-
мы становится трудно модифицировать, когда возникает такая
необходимость. Вторая версия программы поиска самой длинной
строки уступает первой отчасти по этим причинам, а отчасти
потому, что она лишила универсальности две весьма полезные
функции, введя в них имена переменных, с которыми они будут
манипулировать.
Упражнение 1-18
---------------
Проверка в операторе FOR функции GETLINE довольно неук-
люжа. Перепишите программу таким образом, чтобы сделать эту
проверку более ясной, но сохраните при этом то же самое по-
ведение в конце файла и при переполнении буфера. Является ли
это поведение самым разумным?
На данном этапе мы обсудили то, что можно бы назвать
традиционным ядром языка "C". Имея эту горсть строительных
блоков, можно писать полезные программы весьма значительного
размера, и было бы вероятно неплохой идеей, если бы вы за-
держались здесь на какое-то время и поступили таким образом:
следующие ниже упражнения предлагают вам ряд программ нес-
колько большей сложности, чем те, которые были приведены в
этой главе.
После того как вы овладеете этой частью "C", приступайте
к чтению следующих нескольких глав. Усилия, которые вы при
этом затратите, полностью окупятся, потому что в этих главах
обсуждаются именно те стороны "C", где мощь и выразитель-
ность языка начинает становиться очевидной.
Упражнение 1-19
---------------
Напишите программу DETAB, которая заменяет табуляции во
вводе на нужное число пробелов так, чтобы промежуток дости-
гал следующей табуляционной остановки. Предположите фиксиро-
ванный набор табуляционных остановок, например, через каждые
N позиций.
Упражнение 1-20
----------------
Напишите программу ENTAB, которая заменяет строки пробе-
лов минимальным числом табуляций и пробелов, достигая при
этом тех же самых промежутков. Используйте те же табуляцион-
ные остановки, как и в DETAB.
Упражнение 1-21
----------------
Напишите программу для "сгибания" длинных вводимых строк
после последнего отличного от пробела символа, стоящего до
столбца N ввода, где N - параметр. убедитесь, что ваша прог-
рамма делает что-то разумное с очень длинными строками и в
случае, когда перед указанным столбцом нет ни табуляций, ни
пробелов.
Упражнение 1-22
----------------
Напишите программу удаления из "C"-программы всех ком-
ментариев. Не забывайте аккуратно обращаться с "закавыченны-
ми" строками и символьными константами.
Упражнение 1-23
----------------
Напишите программу проверки "C"-программы на элементар-
ные синтаксические ошибки, такие как несоответствие круглых,
квадратных и фигурных скобок. Не забудьте о кавычках, как
одиночных, так и двойных, и о комментариях. (Эта программа
весьма сложна, если вы будете писать ее для самого общего
случая).
Переменные и константы являются основными объектами, с
которыми оперирует программа. Описания перечисляют перемен-
ные, которые будут использоваться, указывают их тип и, воз-
можно, их начальные значения. Операции определяют, что с ни-
ми будет сделано. выражения объединяют переменные и констан-
ты для получения новых значений. Все это - темы настоящей
главы.
Хотя мы этого сразу прямо не сказали, существуют некото-
рые ограничения на имена переменных и символических конс-
тант. Имена составляются из букв и цифр; первый символ дол-
жен быть буквой. Подчеркивание "_" тоже считается буквой;
это полезно для удобочитаемости длинных имен переменных.
Прописные и строчные буквы различаются; традиционная практи-
ка в "с" - использовать строчные буквы для имен переменных,
а прописные - для символических констант.
Играют роль только первые восемь символов внутреннего
имени, хотя использовать можно и больше. Для внешних имен,
таких как имена функций и внешних переменных, это число мо-
жет оказаться меньше восьми, так как внешние имена использу-
ются различными ассемблерами и загрузчиками. Детали приво-
дятся в приложении а. Кроме того, такие ключевые слова как
IF, ELSE, INT, FLOAT и т.д., зарезервированы: вы не можете
использовать их в качестве имен переменных. (Они пишутся
строчными буквами).
Конечно, разумно выбирать имена переменных таким обра-
зом, чтобы они означали нечто, относящееся к назначению пе-
ременных, и чтобы было менее вероятно спутать их при написа-
нии.
Языке "C" имеется только несколько основных типов дан-
ных:
CHAR один байт, в котором может находиться один символ из
внутреннего набора символов.
INT Целое, обычно соответствующее естественному размеру це-
лых в используемой машине.
FLOAT С плавающей точкой одинарной точности.
DOUBLE С плавающей точкой двойной точности.
Кроме того имеется ряд квалификаторов, которые можно ис-
пользовать с типом INT: SHORT (короткое), LONG (длинное) и
UNSIGNED (без знака). Квалификаторы SHORT и LONG указывают
на различные размеры целых. Числа без знака подчиняются за-
конам арифметики по модулю 2 в степени N, где N - число би-
тов в INT; числа без знаков всегда положительны. Описания с
квалификаторами имеют вид:
SHORT INT X;
LONG INT Y;
UNSIGNED INT Z;
Cлово INT в таких ситуациях может быть опущено, что
обычно и делается.
Количество битов, отводимых под эти объекты зависит от
имеющейся машины; в таблице ниже приведены некоторые харак-
терные значения.
Таблица 1
---------------------------------------------------------
!
DEC PDP-11 HONEYWELL IBM 370 INTERDATA !
6000 8/32 !
!
ASCII ASCII EBCDIC ASCII !
!
CHAR 8-BITS 9-BITS 8-BITS 8-BITS !
INT 16 36 32 32 !
SHORT 16 36 16 16 !
LONG 32 36 32 32 !
FLOAT 32 36 32 32 !
DOUBLE 64 72 64 64 !
!
---------------------------------------------------------
Цель состоит в том, чтобы SHORT и LONG давали возмож-
ность в зависимости от практических нужд использовать раз-
личные длины целых; тип INT отражает наиболее "естественный"
размер конкретной машины. Как вы видите, каждый компилятор
свободно интерпретирует SHORT и LONG в соответствии со свои-
ми аппаратными средствами. Все, на что вы можете твердо по-
лагаться, это то, что SHORT не длиннее, чем LONG.
Константы типа INT и FLOAT мы уже рассмотрели. Отметим
еще только, что как обычная
123.456е-7,
так и "научная" запись
0.12е3
для FLOAT является законной.
Каждая константа с плавающей точкой считается имеющей
тип DOUBLE, так что обозначение "E" служит как для FLOAT,
так и для DOUBLE.
Длинные константы записываются в виде 123L. Обычная це-
лая константа, которая слишком длинна для типа INT, рассмат-
ривается как LONG.
Существует система обозначений для восьмеричных и шест-
надцатеричных констант: лидирующий 0(нуль) в константе типа
INT указывает на восьмеричную константу, а стоящие впереди
0X соответствуют шестнадцатеричной константе. Например, де-
сятичное число 31 можно записать как 037 в восьмеричной фор-
ме и как 0X1F в шестнадцатеричной. Шестнадцатеричные и вось-
меричные константы могут также заканчиваться буквой L, что
делает их относящимися к типу LONG.
Символьная константа - это один символ, заключенный в
одинарные кавычки, как, например, 'х'. Значением символьной
константы является численное значение этого символа во внут-
реннем машинном наборе символов. Например, в наборе символов
ASCII символьный нуль, или '0', имеет значение 48, а в коде
EBCDIC - 240, и оба эти значения совершенно отличны от числа
0. Написание '0' вместо численного значения, такого как 48
или 240, делает программу не зависящей от конкретного чис-
ленного представления этого символа в данной машине. Сим-
вольные константы точно так же участвуют в численных опера-
циях, как и любые другие числа, хотя наиболее часто они ис-
пользуются в сравнении с другими символами. Правила преобра-
зования будут изложены позднее.
Некоторые неграфические символы могут быть представлены
как символьные константы с помощью условных последователь-
ностей, как, например, \N (новая строка), \T (табуляция), \0
(нулевой символ), \\ (обратная косая черта), \' (одинарная
кавычка) и т.д. Хотя они выглядят как два символа, на самом
деле являются одним. Кроме того, можно сгенерировать произ-
вольную последовательность двоичных знаков размером в байт,
если написать
'\DDD'
где DDD - от одной до трех восьмеричных цифр, как в
#DEFINE FORMFEED '\014' /* FORM FEED */
Символьная константа '\0', изображающая символ со значе-
нием 0, часто записывается вместо целой константы 0 , чтобы
подчеркнуть символьную природу некоторого выражения.
Константное выражение - это выражение, состоящее из од-
них констант. Такие выражения обрабатываются во время компи-
ляции, а не при прогоне программы, и соответственно могут
быть использованы в любом месте, где можно использовать кон-
станту, как, например в
#DEFINE MAXLINE 1000
CHAR LINE[MAXLINE+1];
или
SECONDS = 60 * 60 * HOURS;
Строчная константа - это последовательность, состоящая
из нуля или более символов, заключенных в двойные кавычки,
как, например,
"I AM A STRING" /* я - строка */
или
"" /* NULL STRING */ /* нуль-строка */
Кавычки не являются частью строки, а служат только для
ее ограничения. те же самые условные последовательности, ко-
торые использовались в символьных константах, применяются и
в строках; символ двойной кавычки изображается как \".
С технической точки зрения строка представляет собой
массив, элементами которого являются отдельные символы. Что-
бы программам было удобно определять конец строки, компиля-
тор автоматически помещает в конец каждой строки нуль-символ
\0. Такое представление означает, что не накладывается конк-
ретного ограничения на то, какую длину может иметь строка, и
чтобы определить эту длину, программы должны просматривать
строку полностью. При этом для физического хранения строки
требуется на одну ячейку памяти больше, чем число заключен-
ных в кавычки символов. Следующая функция 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.
Все переменные должны быть описаны до их использования,
хотя некоторые описания делаются неявно, по контексту. Опи-
сание состоит из спецификатора типа и следующего за ним
списка переменных, имеющих этот тип, как, например,
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;
Если рассматриваемая переменная является внешней или
статической, то инициализация проводится только один раз,
согласно концепции до начала выполнения программы. Инициали-
зируемым явно автоматическим переменным начальные значения
присваиваются при каждом обращении к функции, в которой они
описаны. Автоматические переменные, не инициализируемые яв-
но, имеют неопределенные значения, (т.е. мусор). Внешние и
статические переменные по умолчанию инициализируются нулем,
но, тем не менее, их явная инициализация является признаком
хорошего стиля.
Мы продолжим обсуждение вопросов инициализации, когда
будем описывать новые типы данных.
Бинарными арифметическими операциями являются +, -, *, /
и операция деления по модулю %. Имеется унарная операция -,
но не существует унарной операции +.
При делении целых дробная часть отбрасывается. Выражение
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. Это редко приводит к како-
му-либо расхождению, но если необходимо обеспечить строго
определенный порядок, то нужно использовать явные промежу-
точные переменные.
Действия, предпринимаемые при переполнении и антипере-
полнении (т.е. При получении слишком маленького по абсолют-
ной величине числа), зависят от используемой машины.
Операциями отношения являются
=> > =< <
все они имеют одинаковое старшинство. Непосредственно за ни-
ми по уровню старшинства следуют операции равенства и нера-
венства:
== !=
которые тоже имеют одинаковое старшинство. операции отноше-
ния младше арифметических операций, так что выражения типа
I<LIM-1 понимаются как I<(LIM-1), как и предполагается.
Логические связки && и \!\! более интересны. Выражения,
связанные операциями && и \!\!, вычисляются слева направо,
причем их рассмотрение прекращается сразу же как только ста-
новится ясно, будет ли результат истиной или ложью. учет
этих свойств очень существенен для написания правильно рабо-
тающих программ. Рассмотрим, например, оператор цикла в счи-
тывающей строку функции GETLINE, которую мы написали в главе
1.
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, не используя операции &&.
Если в выражениях встречаются операнды различных типов,
то они преобразуются к общему типу в соответствии с неболь-
шим набором правил. В общем, автоматически производятся
только преобразования, имеющие смысл, такие как, например,
преобразование целого в плавающее в выражениях типа F+I. Вы-
ражения же, лишенные смысла, такие как использование пере-
менной типа FLOAT в качестве индекса, запрещены.
Во-первых, типы CHAR и INT могут свободно смешиваться в
арифметических выражениях: каждая переменная типа CHAR авто-
матически преобразуется в INT. Это обеспечивает значительную
гибкость при проведении определенных преобразований симво-
лов. Примером может служить функция ATOI, которая ставит в
соответствие строке цифр ее численный эквивалент.
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(C) /* CONVERT C TO LOWER CASE; ASCII ONLY */
INT C;
{
IF ( C >= 'A' && C <= 'Z' )
RETURN( C + '@' - 'A');
ELSE /*@ Записано вместо 'A' строчного*/
RETURN(C);
}
Эта функция правильно работает при коде ASCII, потому что
численные значения, соответствующие в этом коде прописным и
строчным буквам, отличаются на постоянную величину, а каждый
алфавит является сплошным - между а и Z нет ничего, кроме
букв. Это последнее замечание для набора символов EBCDIC
систем IBM 360/370 оказывается несправедливым, в силу чего
эта программа на таких системах работает неправильно - она
преобразует не только буквы.
При преобразовании символьных переменных в целые возни-
кает один тонкий момент. Дело в том, что сам язык не указы-
вает, должны ли переменным типа CHAR соответствовать числен-
ные значения со знаком или без знака. Может ли при преобра-
зовании CHAR в INT получиться отрицательное целое? К сожале-
нию, ответ на этот вопрос меняется от машины к машине, отра-
жая расхождения в их архитектуре. На некоторых машинах
(PDP-11, например) переменная типа CHAR, крайний левый бит
которой содержит 1, преобразуется в отрицательное целое
("знаковое расширение"). На других машинах такое преобразо-
вание сопровождается добавлением нулей с левого края, в ре-
зультате чего всегда получается положительное число.
Определение языка "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.
- Затем, если один из операндов имеет тип 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). В этой конструкции, имеющей
вид
(имя типа) выражение
Выражение преобразуется к указанному типу по правилам
преобразования, изложенным выше. Фактически точный смысл
операции перевода можно описать следующим образом: выражение
как бы присваивается некоторой переменной указанного типа,
которая затем используется вместо всей конструкции. Напри-
мер, библиотечная процедура SQRT ожидает аргумента типа
DOUBLE и выдаст бессмысленный ответ, если к ней по небреж-
ности обратятся с чем-нибудь иным. таким образом, если N -
целое, то выражение
SQRT((DOUBLE) N)
до передачи аргумента функции SQRT преобразует N к типу
DOUBLE. (Отметим, что операция перевод преобразует значение
N в надлежащий тип; фактическое содержание переменной N при
этом не изменяется). Операция перевода имрация перевода име-
ет тот же уровень старшинства, что и другие унарные опера-
ции, как указывается в таблице в конце этой главы.
Упражнение 2-2
---------------
Составьте программу для функции HTOI(S), которая преоб-
разует строку шестнадцатеричных цифр в эквивалентное ей це-
лое значение. При этом допустимыми цифрами являются цифры от
1 до 9 и буквы от а до F.
В языке "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)++ являются незаконными.
некоторая переменная определена, скажем в файле 1, а исполь-
зуется в файле 2, то чтобы связать эти два вхождения пере-
менной, необходимо в файле 2 использовать описание EXTERN.
Этот вопрос подробно обсуждается в главе 4.
Вы должно быть заметили, что мы в этом разделе при ссыл-
ке на внешние переменные очень аккуратно используем слова
описание и определение. "Определение" относится к тому мес-
ту, где переменная фактически заводится и ей выделяется па-
мять; "описание" относится к тем местам, где указывается
природа переменной, но никакой памяти не отводится.
Между прочим, существует тенденция объявлять все, что ни
попадется, внешними переменными, поскольку кажется, что это
упрощает связи, - списки аргументов становятся короче и пе-
ременные всегда присутствуют, когда бы вам они ни понадоби-
лись. Но внешние переменные присутствуют и тогда, когда вы в
них не нуждаетесь. Такой стиль программирования чреват опас-
ностью, так как он приводит к программам, связи данных внут-
ри которых не вполне очевидны. Переменные при этом могут из-
меняться неожиданным и даже неумышленным образом, а програм-
мы становится трудно модифицировать, когда возникает такая
необходимость. Вторая версия программы поиска самой длинной
строки уступает первой отчасти по этим причинам, а отчасти
потому, что она лишила универсальности две весьма полезные
функции, введя в них имена переменных, с которыми они будут
манипулировать.
Упражнение 1-18
---------------
Проверка в операторе FOR функции GETLINE довольно неук-
люжа. Перепишите программу таким образом, чтобы сделать эту
проверку более ясной, но сохраните при этом то же самое по-
ведение в конце файла и при переполнении буфера. Является ли
это поведение самым разумным?
На данном этапе мы обсудили то, что можно бы назвать
традиционным ядром языка "C". Имея эту горсть строительных
блоков, можно писать полезные программы весьма значительного
размера, и было бы вероятно неплохой идеей, если бы вы за-
держались здесь на какое-то время и поступили таким образом:
следующие ниже упражнения предлагают вам ряд программ нес-
колько большей сложности, чем те, которые были приведены в
этой главе.
После того как вы овладеете этой частью "C", приступайте
к чтению следующих нескольких глав. Усилия, которые вы при
этом затратите, полностью окупятся, потому что в этих главах
обсуждаются именно те стороны "C", где мощь и выразитель-
ность языка начинает становиться очевидной.
Упражнение 1-19
---------------
Напишите программу DETAB, которая заменяет табуляции во
вводе на нужное число пробелов так, чтобы промежуток дости-
гал следующей табуляционной остановки. Предположите фиксиро-
ванный набор табуляционных остановок, например, через каждые
N позиций.
Упражнение 1-20
----------------
Напишите программу ENTAB, которая заменяет строки пробе-
лов минимальным числом табуляций и пробелов, достигая при
этом тех же самых промежутков. Используйте те же табуляцион-
ные остановки, как и в DETAB.
Упражнение 1-21
----------------
Напишите программу для "сгибания" длинных вводимых строк
после последнего отличного от пробела символа, стоящего до
столбца N ввода, где N - параметр. убедитесь, что ваша прог-
рамма делает что-то разумное с очень длинными строками и в
случае, когда перед указанным столбцом нет ни табуляций, ни
пробелов.
Упражнение 1-22
----------------
Напишите программу удаления из "C"-программы всех ком-
ментариев. Не забывайте аккуратно обращаться с "закавыченны-
ми" строками и символьными константами.
Упражнение 1-23
----------------
Напишите программу проверки "C"-программы на элементар-
ные синтаксические ошибки, такие как несоответствие круглых,
квадратных и фигурных скобок. Не забудьте о кавычках, как
одиночных, так и двойных, и о комментариях. (Эта программа
весьма сложна, если вы будете писать ее для самого общего
случая).
Переменные и константы являются основными объектами, с
которыми оперирует программа. Описания перечисляют перемен-
ные, которые будут использоваться, указывают их тип и, воз-
можно, их начальные значения. Операции определяют, что с ни-
ми будет сделано. выражения объединяют переменные и констан-
ты для получения новых значений. Все это - темы настоящей
главы.
Хотя мы этого сразу прямо не сказали, существуют некото-
рые ограничения на имена переменных и символических конс-
тант. Имена составляются из букв и цифр; первый символ дол-
жен быть буквой. Подчеркивание "_" тоже считается буквой;
это полезно для удобочитаемости длинных имен переменных.
Прописные и строчные буквы различаются; традиционная практи-
ка в "с" - использовать строчные буквы для имен переменных,
а прописные - для символических констант.
Играют роль только первые восемь символов внутреннего
имени, хотя использовать можно и больше. Для внешних имен,
таких как имена функций и внешних переменных, это число мо-
жет оказаться меньше восьми, так как внешние имена использу-
ются различными ассемблерами и загрузчиками. Детали приво-
дятся в приложении а. Кроме того, такие ключевые слова как
IF, ELSE, INT, FLOAT и т.д., зарезервированы: вы не можете
использовать их в качестве имен переменных. (Они пишутся
строчными буквами).
Конечно, разумно выбирать имена переменных таким обра-
зом, чтобы они означали нечто, относящееся к назначению пе-
ременных, и чтобы было менее вероятно спутать их при написа-
нии.
Языке "C" имеется только несколько основных типов дан-
ных:
CHAR один байт, в котором может находиться один символ из
внутреннего набора символов.
INT Целое, обычно соответствующее естественному размеру це-
лых в используемой машине.
FLOAT С плавающей точкой одинарной точности.
DOUBLE С плавающей точкой двойной точности.
Кроме того имеется ряд квалификаторов, которые можно ис-
пользовать с типом INT: SHORT (короткое), LONG (длинное) и
UNSIGNED (без знака). Квалификаторы SHORT и LONG указывают
на различные размеры целых. Числа без знака подчиняются за-
конам арифметики по модулю 2 в степени N, где N - число би-
тов в INT; числа без знаков всегда положительны. Описания с
квалификаторами имеют вид:
SHORT INT X;
LONG INT Y;
UNSIGNED INT Z;
Cлово INT в таких ситуациях может быть опущено, что
обычно и делается.
Количество битов, отводимых под эти объекты зависит от
имеющейся машины; в таблице ниже приведены некоторые харак-
терные значения.
Таблица 1
---------------------------------------------------------
!
DEC PDP-11 HONEYWELL IBM 370 INTERDATA !
6000 8/32 !
!
ASCII ASCII EBCDIC ASCII !
!
CHAR 8-BITS 9-BITS 8-BITS 8-BITS !
INT 16 36 32 32 !
SHORT 16 36 16 16 !
LONG 32 36 32 32 !
FLOAT 32 36 32 32 !
DOUBLE 64 72 64 64 !
!
---------------------------------------------------------
Цель состоит в том, чтобы SHORT и LONG давали возмож-
ность в зависимости от практических нужд использовать раз-
личные длины целых; тип INT отражает наиболее "естественный"
размер конкретной машины. Как вы видите, каждый компилятор
свободно интерпретирует SHORT и LONG в соответствии со свои-
ми аппаратными средствами. Все, на что вы можете твердо по-
лагаться, это то, что SHORT не длиннее, чем LONG.
Константы типа INT и FLOAT мы уже рассмотрели. Отметим
еще только, что как обычная
123.456е-7,
так и "научная" запись
0.12е3
для FLOAT является законной.
Каждая константа с плавающей точкой считается имеющей
тип DOUBLE, так что обозначение "E" служит как для FLOAT,
так и для DOUBLE.
Длинные константы записываются в виде 123L. Обычная це-
лая константа, которая слишком длинна для типа INT, рассмат-
ривается как LONG.
Существует система обозначений для восьмеричных и шест-
надцатеричных констант: лидирующий 0(нуль) в константе типа
INT указывает на восьмеричную константу, а стоящие впереди
0X соответствуют шестнадцатеричной константе. Например, де-
сятичное число 31 можно записать как 037 в восьмеричной фор-
ме и как 0X1F в шестнадцатеричной. Шестнадцатеричные и вось-
меричные константы могут также заканчиваться буквой L, что
делает их относящимися к типу LONG.
Символьная константа - это один символ, заключенный в
одинарные кавычки, как, например, 'х'. Значением символьной
константы является численное значение этого символа во внут-
реннем машинном наборе символов. Например, в наборе символов
ASCII символьный нуль, или '0', имеет значение 48, а в коде
EBCDIC - 240, и оба эти значения совершенно отличны от числа
0. Написание '0' вместо численного значения, такого как 48
или 240, делает программу не зависящей от конкретного чис-
ленного представления этого символа в данной машине. Сим-
вольные константы точно так же участвуют в численных опера-
циях, как и любые другие числа, хотя наиболее часто они ис-
пользуются в сравнении с другими символами. Правила преобра-
зования будут изложены позднее.
Некоторые неграфические символы могут быть представлены
как символьные константы с помощью условных последователь-
ностей, как, например, \N (новая строка), \T (табуляция), \0
(нулевой символ), \\ (обратная косая черта), \' (одинарная
кавычка) и т.д. Хотя они выглядят как два символа, на самом
деле являются одним. Кроме того, можно сгенерировать произ-
вольную последовательность двоичных знаков размером в байт,
если написать
'\DDD'
где DDD - от одной до трех восьмеричных цифр, как в
#DEFINE FORMFEED '\014' /* FORM FEED */
Символьная константа '\0', изображающая символ со значе-
нием 0, часто записывается вместо целой константы 0 , чтобы
подчеркнуть символьную природу некоторого выражения.
Константное выражение - это выражение, состоящее из од-
них констант. Такие выражения обрабатываются во время компи-
ляции, а не при прогоне программы, и соответственно могут
быть использованы в любом месте, где можно использовать кон-
станту, как, например в
#DEFINE MAXLINE 1000
CHAR LINE[MAXLINE+1];
или
SECONDS = 60 * 60 * HOURS;
Строчная константа - это последовательность, состоящая
из нуля или более символов, заключенных в двойные кавычки,
как, например,
"I AM A STRING" /* я - строка */
или
"" /* NULL STRING */ /* нуль-строка */
Кавычки не являются частью строки, а служат только для
ее ограничения. те же самые условные последовательности, ко-
торые использовались в символьных константах, применяются и
в строках; символ двойной кавычки изображается как \".
С технической точки зрения строка представляет собой
массив, элементами которого являются отдельные символы. Что-
бы программам было удобно определять конец строки, компиля-
тор автоматически помещает в конец каждой строки нуль-символ
\0. Такое представление означает, что не накладывается конк-
ретного ограничения на то, какую длину может иметь строка, и
чтобы определить эту длину, программы должны просматривать
строку полностью. При этом для физического хранения строки
требуется на одну ячейку памяти больше, чем число заключен-
ных в кавычки символов. Следующая функция 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.
Все переменные должны быть описаны до их использования,
хотя некоторые описания делаются неявно, по контексту. Опи-
сание состоит из спецификатора типа и следующего за ним
списка переменных, имеющих этот тип, как, например,
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;
Если рассматриваемая переменная является внешней или
статической, то инициализация проводится только один раз,
согласно концепции до начала выполнения программы. Инициали-
зируемым явно автоматическим переменным начальные значения
присваиваются при каждом обращении к функции, в которой они
описаны. Автоматические переменные, не инициализируемые яв-
но, имеют неопределенные значения, (т.е. мусор). Внешние и
статические переменные по умолчанию инициализируются нулем,
но, тем не менее, их явная инициализация является признаком
хорошего стиля.
Мы продолжим обсуждение вопросов инициализации, когда
будем описывать новые типы данных.
Бинарными арифметическими операциями являются +, -, *, /
и операция деления по модулю %. Имеется унарная операция -,
но не существует унарной операции +.
При делении целых дробная часть отбрасывается. Выражение
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. Это редко приводит к како-
му-либо расхождению, но если необходимо обеспечить строго
определенный порядок, то нужно использовать явные промежу-
точные переменные.
Действия, предпринимаемые при переполнении и антипере-
полнении (т.е. При получении слишком маленького по абсолют-
ной величине числа), зависят от используемой машины.
Операциями отношения являются
=> > =< <
все они имеют одинаковое старшинство. Непосредственно за ни-
ми по уровню старшинства следуют операции равенства и нера-
венства:
== !=
которые тоже имеют одинаковое старшинство. операции отноше-
ния младше арифметических операций, так что выражения типа
I<LIM-1 понимаются как I<(LIM-1), как и предполагается.
Логические связки && и \!\! более интересны. Выражения,
связанные операциями && и \!\!, вычисляются слева направо,
причем их рассмотрение прекращается сразу же как только ста-
новится ясно, будет ли результат истиной или ложью. учет
этих свойств очень существенен для написания правильно рабо-
тающих программ. Рассмотрим, например, оператор цикла в счи-
тывающей строку функции GETLINE, которую мы написали в главе
1.
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, не используя операции &&.
Если в выражениях встречаются операнды различных типов,
то они преобразуются к общему типу в соответствии с неболь-
шим набором правил. В общем, автоматически производятся
только преобразования, имеющие смысл, такие как, например,
преобразование целого в плавающее в выражениях типа F+I. Вы-
ражения же, лишенные смысла, такие как использование пере-
менной типа FLOAT в качестве индекса, запрещены.
Во-первых, типы CHAR и INT могут свободно смешиваться в
арифметических выражениях: каждая переменная типа CHAR авто-
матически преобразуется в INT. Это обеспечивает значительную
гибкость при проведении определенных преобразований симво-
лов. Примером может служить функция ATOI, которая ставит в
соответствие строке цифр ее численный эквивалент.
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(C) /* CONVERT C TO LOWER CASE; ASCII ONLY */
INT C;
{
IF ( C >= 'A' && C <= 'Z' )
RETURN( C + '@' - 'A');
ELSE /*@ Записано вместо 'A' строчного*/
RETURN(C);
}
Эта функция правильно работает при коде ASCII, потому что
численные значения, соответствующие в этом коде прописным и
строчным буквам, отличаются на постоянную величину, а каждый
алфавит является сплошным - между а и Z нет ничего, кроме
букв. Это последнее замечание для набора символов EBCDIC
систем IBM 360/370 оказывается несправедливым, в силу чего
эта программа на таких системах работает неправильно - она
преобразует не только буквы.
При преобразовании символьных переменных в целые возни-
кает один тонкий момент. Дело в том, что сам язык не указы-
вает, должны ли переменным типа CHAR соответствовать числен-
ные значения со знаком или без знака. Может ли при преобра-
зовании CHAR в INT получиться отрицательное целое? К сожале-
нию, ответ на этот вопрос меняется от машины к машине, отра-
жая расхождения в их архитектуре. На некоторых машинах
(PDP-11, например) переменная типа CHAR, крайний левый бит
которой содержит 1, преобразуется в отрицательное целое
("знаковое расширение"). На других машинах такое преобразо-
вание сопровождается добавлением нулей с левого края, в ре-
зультате чего всегда получается положительное число.
Определение языка "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.
- Затем, если один из операндов имеет тип 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). В этой конструкции, имеющей
вид
(имя типа) выражение
Выражение преобразуется к указанному типу по правилам
преобразования, изложенным выше. Фактически точный смысл
операции перевода можно описать следующим образом: выражение
как бы присваивается некоторой переменной указанного типа,
которая затем используется вместо всей конструкции. Напри-
мер, библиотечная процедура SQRT ожидает аргумента типа
DOUBLE и выдаст бессмысленный ответ, если к ней по небреж-
ности обратятся с чем-нибудь иным. таким образом, если N -
целое, то выражение
SQRT((DOUBLE) N)
до передачи аргумента функции SQRT преобразует N к типу
DOUBLE. (Отметим, что операция перевод преобразует значение
N в надлежащий тип; фактическое содержание переменной N при
этом не изменяется). Операция перевода имрация перевода име-
ет тот же уровень старшинства, что и другие унарные опера-
ции, как указывается в таблице в конце этой главы.
Упражнение 2-2
---------------
Составьте программу для функции HTOI(S), которая преоб-
разует строку шестнадцатеричных цифр в эквивалентное ей це-
лое значение. При этом допустимыми цифрами являются цифры от
1 до 9 и буквы от а до F.
В языке "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)++ являются незаконными.