Керниган и Ритчи - Язык программирования Си (793773), страница 9
Текст из файла (страница 9)
Вот почему мы используем int, а не char.EOF — целая константа, определенная в <stdio.h>. Какое значение имеет эта константа — неважно, лишьбы оно отличалось от любого из возможных значений типа char. Использование именованной константы сунифицированным именем гарантирует, что программа не будет зависеть от конкретного числовогозначения, которое, возможно, в других Си-системах будет иным.Программу копирования можно написать более сжато. В Си любое присваивание, напримерс = getchar()трактуется как выражение со значением, равным значению левой части после присваивания. Это значит, чтоприсваивание может встречаться внутри более сложного выражения.
Если присваивание переменной cрасположить в проверке условия цикла while, то программу копирования можно будет записать вследующем виде:#include <stdio.h>/* копирование ввода на вывод; 2-я версия */main (){int с;while ((с = getchar()) != EOF)putchar (c);}Цикл while, пересылая в с полученное от getchar значение, сразу же проверяет: не является ли оно"концом файла".
Если это не так, выполняется тело цикла while и печатается символ. По окончании вводазавершается работа цикла while, а тем самым и main.В данной версии ввод "централизован" — в программе имеется только одно обращение к getchar. Врезультате она более компактна и легче воспринимается при чтении. Вам часто придется сталкиваться с такойформой записи, где присваивание делается вместе с проверкой. (Чрезмерное увлечение ею, однако, можетзапутать программу, поэтому мы постараемся пользоваться указанной формой разумно.)Скобки внутри условия, вокруг присваивания, необходимы.
Приоритет != выше, чем приоритет =, из чегоследует, что при отсутствии скобок проверка != будет выполняться до операции присваивания =. Такимобразом, записьс = getchar() != EOFэквивалентна записис = (getchar() != EOF)А это совсем не то, что нам нужно: переменной c будет присваиваться 0 или 1 в зависимости от того, встретитили не встретит getchar признак конца файла.
(Более подробно об этом см. в главе 2.)Упражнение 1.6. Убедитесь в том, что выражение getchar() != EOF получает значение 0 или 1 .Упражнение 1.7. Напишите программу, печатающую значение EOF.1.5.2. Подсчет символовСледующая программа занимается подсчетом символов; она имеет много сходных черт с программойкопирования.#include <stdio.h>/* подсчет вводимых символов; 1-я версия */main (){long nc;nc = 0;while (getchar() != EOF)++nc;printf ("%ld\n", nc);}Инструкция++nc;представляет новый оператор ++, который означает увеличить на единицу.
Вместо этого можно было бынаписать nc = nc+1, но ++nc намного короче, а часто и эффективнее. Существует аналогичный оператор -, означающий уменьшить на единицу. Операторы ++ и -- могут быть как префиксными (++nc), так ипостфиксными (nc++). Как будет показано в главе 2, эти две формы в выражениях имеют разные значения, нои ++nc, и nc++ добавляют к nc единицу. В данном случае мы остановились на префиксной записи.Программа подсчета символов накапливает сумму в переменной типа long.
Целые типа long имеют неменее 32 битов. Хотя на некоторых машинах типы int и long имеют одинаковый размер, существуют,однако, машины, в которых int занимает 16 битов с максимально возможным значением 32767, а это —сравнительно маленькое число, и счетчик типа int может переполниться. Спецификация %ld в printfуказывает, что соответствующий аргумент имеет тип long.Возможно охватить еще больший диапазон значений, если использовать тип double (т.
е. float с двойнойточностью). Применим также инструкцию for вместо while, чтобы продемонстрировать другой способнаписания цикла.#include <stdio.h>/* подсчет вводимых символов; 2-я версия */main(){double nc;for (nc = 0; getchar() != EOF; ++nc);printf ("%.0f\n", nc);}В printf спецификатор %f применяется как для float, так и для double; спецификатор %.0f означаетпечать без десятичной точки и дробной части (последняя в нашем случае отсутствует).Тело указанного fоr-цикла пусто, поскольку кроме проверок и приращений счетчика делать ничего ненужно.
Но правила грамматики Си требуют, чтобы for-цикл имел тело. Выполнение этого требованияобеспечивает изолированная точка с запятой, называемая пустой инструкцией. Мы поставили точку с запятойна отдельной строке для большей наглядности.Наконец, заметим, что если ввод не содержит ни одного символа, то при первом же обращении к getcharусловие в while или for не будет выполнено и программа выдаст нуль, что и будет правильнымрезультатом.
Это важно. Одно из привлекательных свойств циклов while и for состоит в том, что условиепроверяется до того, как выполняется тело цикла. Если ничего делать не надо, то ничего делаться и не будет,пусть даже тело цикла не выполнится ни разу. Программа должна вести себя корректно и при нулевомколичестве вводимых символов. Само устройство циклов while и for дает дополнительную уверенность вправильном поведении программы в случае граничных условий.1.5.3.
Подсчет строкСледующая программа подсчитывает строки. Как упоминалось выше, стандартная библиотека обеспечиваеттакую модель ввода-вывода, при которой входной текстовый поток состоит из последовательности строк,каждая из которых заканчивается символом новой строки.
Следовательно, подсчет строк сводится к подсчетучисла символов новой строки.#include <stdio.h>/* подсчет строк входного потока */main(){int с, nl;nl = 0;while ((с = getchar()) != EOF)if (c == '\n')++nl;printf ("%d\n", nl);}Тело цикла теперь образует инструкция if, под контролем которой находится увеличение счетчика nl наединицу. Инструкция if проверяет условие в скобках и, если оно истинно, выполняет следующую за ниминструкцию (или группу инструкций, заключенную в фигурные скобки). Мы опять делаем отступы в текстепрограммы, чтобы показать, что чем управляется.Двойной знак равенства в языке Си обозначает оператор "равно" (он аналогичен оператору = в Паскале и .EQ.
в Фортране). Удваивание знака = в операторе проверки на равенство сделано для того, чтобы отличитьего от единичного =, используемого в Си для обозначения присваивания. Предупреждаем: начинающиепрограммировать на Си иногда пишут =, а имеют в виду ==. Как мы увидим в главе 2, в этом случаерезультатом будет обычно вполне допустимое по форме выражение, на которое компилятор не выдастникаких предупреждающих сообщений3.Символ, заключенный в одиночные кавычки, представляет собой целое значение, равное коду этого символа(в кодировке, принятой на данной машине). Это так называемая символьная константа.
Существует и другойспособ для написания маленьких целых значений. Например, 'А' есть символьная константа; в наборесимволов ASCII ее значение равняется 65 — внутреннему представлению символа А. Конечно, 'А' в роликонстанты предпочтительнее, чем 65, поскольку смысл первой записи более очевиден, и она не зависит отконкретного способа кодировки символов.Эскейп-последовательности, используемые в строковых константах, допускаются также и в символьныхконстантах. Так, '\n' обозначает код символа новой строки, который в ASCII равен 10. Следует обратитьособое внимание на то, что '\n' обозначает один символ (код которого в выражении рассматривается какцелое значение), в то время как "\n" — строковая константа, в которой чисто случайно указан один символ.Более подробно различие между символьными и строковыми константами разбирается в главе 2.Упражнение 1.8.
Напишите программу для подсчета пробелов, табуляций и новых строк.Упражнение 1.9. Напишите программу, копирующую символы ввода в выходной поток и заменяющуюстоящие подряд пробелы на один пробел.Упражнение 1.10. Напишите программу, копирующую вводимые символы в выходной поток с заменойсимвола табуляции на \t, символа забоя на \b и каждой обратной наклонной черты на \\. Это сделаетвидимыми все символы табуляции и забоя.1.5.4.
Подсчет словЧетвертая из нашей серии полезных программ подсчитывает строки, слова и символы, причем под словомздесь имеется в виду любая строка символов, не содержащая в себе пробелов, табуляций и символов новойстроки. Эта программа является упрощенной версией программы wc системы UNIX.#include <stdio.h>#define IN 1 /* внутри слова */#define OUT 0 /* вне слова *//* подсчет строк, слов и символов */main (){int с, nl, nw, nc, state;state = OUT;nl = nw = nc = 0;while ((c = getchar()) != EOF) {++nc;if (c == '\n' )++nl;3Современные компиляторы, как правило, выдают предупреждение о возможной ошибке.
— Примеч. ред.if (c == " " || c == '\n' || с == '\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 = пс = 0;устанавливает все три переменные в нуль. Такая запись не является какой-то особой конструкцией идопустима потому, что присваивание есть выражение со своим собственным значением, а операцииприсваивания выполняются справа налево. Указанная строка эквивалентнаnl = (nw = (nc = 0));Оператор || означает ИЛИ, так что строкаif (с == ' ' || с == '\n' || с == '\t')читается как "если с есть пробел, или с есть новая строка, или с есть табуляция".
(Напомним, что видимаяэскейп-последовательность \t обозначает символ табуляции.) Существует также оператор &&, означающийИ. Его приоритет выше, чем приоритет ||. Выражения, связанные операторами && или ||, вычисляютсяслева направо; при этом гарантируется, что вычисления сразу прервутся, как только будет установленаистинность или ложность условия. Если c есть пробел, то дальше проверять, является значение с символомновой строки или же табуляции, не нужно. В этом частном случае данный способ вычислений не столь важен,но он имеет значение в более сложных ситуациях, которые мы вскоре рассмотрим.В примере также встречается слово else, которое указывает на альтернативные действия, выполняемые вслучае, когда условие, указанное в if, не является истинным.














