Двойной знак равенства в языке Си обозначает оператор "равно" (он аналогичен оператору = в Паскале и .EQ. в Фортране). Удваивание знака = в операторе проверки на равенство сделано для того, чтобы отличить его от единичного =, используемого в Си для обозначения присваивания. Предупреждаем: начинающие программировать на Си иногда пишут =, а имеют в виду ==. Как мы увидим в главе 2, в этом случае результатом будет обычно вполне допустимое по форме выражение, на которое компилятор не выдаст никаких предупреждающих сообщений (Современные компиляторы, как правило, выдают предупреждение о возможной ошибке. - Примеч. ред.).
Символ, заключенный в одиночные кавычки, представляет собой целое значение, равное коду этого символа (в кодировке, принятой на данной машине). Это так называемая символьная константа. Существует и другой способ для написания маленьких целых значений. Например, 'A' есть символьная константа, в наборе символов ASCII ее значение равняется 65 - внутреннему представлению символа A. Конечно, 'A' в роли константы предпочтительнее, чем 65, поскольку смысл первой записи более очевиден, и она не зависит от конкретного способа кодировки символов.
Эскейп-последовательности, используемые в строковых константах, допускаются также и в символьных константах. Так, '\n' обозначает код символа новой строки, который в ASCII равен 10. Следует обратить особое внимание на то, что '\n' обозначает один символ (код которого в выражении рассматривается как целое значение), в то время как "\n" - строковая константа, в которой чисто случайно указан один символ. Более подробно различие между символьными и строковыми константами разбирается в главе 2.
Упражнение 1.8. Напишите программу для подсчета пробелов, табуляций и новых строк.
Упражнение 1.9. Напишите программу, копирующую символы ввода в выходной поток и заменяющую стоящие подряд пробелы на один пробел.
Упражнение 1.10. Напишите программу, копирующую вводимые символы в выходной поток с заменой символа табуляции на \t, символа забоя на \b и каждой обратной наклонной черты на \\. Это сделает видимыми все символы табуляции и забоя.
1.5.4 Подсчет слов
wc#include stdio.h
#define IN 1 /* внутри слова */
#define OUT 0 /* вне слова */
/* подсчет строк, слов и символов */
main()
{
int с, nl, nw, nc, state;
state = OUT;
nl = nw = nc = 0;
while ((с = getchar()) != EOF) {
++nc;
if (c == '\n')
++nl;
if (c == ' ' || c == '\n' || c == '\t')
state = OUT;
else if (state == OUT) {
state = IN;
++nw;
}
}
printf("%d %d %d\n", nl, nw, nc);
}
Каждый раз, встречая первый символ слова, программа изменяет значение счетчика слов на 1. Переменная state фиксирует текущее состояние - находимся мы внутри или вне слова. Вначале ей присваивается значение OUT, что соответствует состоянию "вне слова". Мы предпочитаем пользоваться именованными константами IN и OUT, а не собственно значениями 1 и 0, чтобы сделать программу более понятной. В такой маленькой программе этот прием мало что дает, но в большой программе увеличение ее ясности окупает незначительные дополнительные усилия, потраченные на то, чтобы писать программу в таком стиле с самого начала. Вы обнаружите,
что большие изменения гораздо легче вносить в те программы, в которых магические числа встречаются только в виде именованных констант.
Строка
nl = nw = nc = 0;
устанавливает все три переменные в нуль. Такая запись не является какой-то особой конструкцией и допустима потому, что присваивание есть выражение со своим собственным значением, а операции присваивания выполняются справа налево. Указанная строка эквивалентна
nl = (nw = (nc = 0));
Оператор || означает ИЛИ, так что строка
if (c == ' ' || c == '\n' || c == '\t')
читается как "если c есть пробел, или c есть новая строка, или c есть табуляция". (Напомним, что видимая эскейп-последовательность \t обозначает символ табуляции.) Существует также оператор &&, означающий И. Его приоритет выше, чем приоритет ||. Выражения, связанные операторами && или ||, вычисляются слева направо; при этом гарантируется, что вычисления сразу прервутся, как только будет установлена истинность или ложность условия. Если c есть пробел, то дальше проверять, является значение c символом новой строки или же табуляции, не нужно. В этом частном случае данный способ вычислений не столь важен, но он имеет значение в более сложных ситуациях, которые мы вскоре рассмотрим.
В примере также встречается слово else, которое указывает на альтернативные действия, выполняемые в случае, когда условие, указанное в if, не является истинным. В общем виде условная инструкция записывается так:
if (выражение)
инструкция1
else
инструкция2
В конструкции if-else выполняется одна и только одна из двух инструкций. Если выражение истинно, то выполняется инструкция1, если нет, то - инструкция2. Каждая из этих двух инструкций представляет собой либо одну инструкцию, либо несколько, заключенных в фигурные скобки. В нашей программе после else стоит инструкция if, управляющая двумя такими инструкциями.
Упражнение 1.11. Как протестировать программу подсчета слов? Какой ввод вероятнее всего обнаружит ошибки, если они были допущены?