Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
"glymxck" и "r2d2" - это слова. Пeременная word будет использоваться для хранения указания о том, является ли введенный символ частью данного слова или началом следующего. Появление "пустого символа" (которым может быть пробел, табуляция или "новая строка") служит признаком конца слова. Тогда следующий "непустой" символ будет означать начало нового слова, и мы сможем увеличить значение счетчика слов на 1. Вот эта программа: #include #define YES 1 #define NO 0 main( ) { int ch; /* введенный символ */ long nc = 0L; /* число символов */ int n1 = 0; /* число строк */ int nw = 0; /* число слов */ int word = NO; /* = = YES, если содержимое ch - часть слова */ while((ch = getchar( )) != EOF) { nc++ ; / * подсчет символов * / if (ch = = '\n' ) nl++; /* подсчет строк */ if (ch != ' ' && ch != '\n' && ch !='\t' && word ==NO) { word = YES; /* начало нового слова */ nw++; /* подсчет слов */ } if ((ch = = ' ' || ch = = '\n' || ch = = '\t' ) && word == YES) word = NO; /* достигнут конец слова */ } 134 printf(" символов = %1d, слов = %d, строк = %d\n", nc, nw, nl); } Поскольку существуют три различных "пустых символа", мы должны использовать логические операции для проверки всех трех возможностей. Рассмотрим, например, следующую строку: if(ch != ' ' && ch != '\n' && ch != '\t ' && word == NO). В ней говорится: "если содержимое ch - не пробел, и не новая строка, и не табуляция, и не первый символ слова". (Первые три условия эквивалентны проверке, не является ли содержимое ch пустым символом). Выполнение всех четырех условий служит признаком начала нового слова, и значение переменной nw увеличивается. Если мы в середине слова, то первые три условия оказываются вы полненными, но значением переменной word окажется признак YES, и значение переменной nw не увеличивается. Когда в процессе ввода встретится очередной "пустой" символ, переменной word будет вновь присвоен признак NO. Просмотрите программу и проверьте, правильно ли она интерпретирует случаи, когда между словами находится несколько "пустых" символов подряд. Если вы захотите применить эту программу для работы с файлами, используйте операции переключения. ПРОГРАММА, "РИСУЮЩАЯ" СИМВОЛАМИ Далее Содержание Давайте теперь займемся чем нибудь менее утилитарным и более декоративным. Нашей целью является создание программы, с помощью которой вы сможете рисовать на экране геометрические фигуры, заполненные символами. Каждая выводимая строка представляет собой сплошной рад одинаковых символов. Нам предоставляется возмжность выбора символа, длины строки, а текже начальной позиции, с которой она выводиться на печать. Программа читает указываемые параметры до тех пор, пока не встретит признак EOF. Текст программы представлен на рис. 7.5. Положим, мы вызываем программу sketcher. Чтобы ее выполнить, мы набираем на клавиатуре ее имя, затем вводим символ и два числа. На экране появляется отклик, после чего мы вводим его набор параметров, отклик появляется вновь, и так до тех пор пока мы не введем признак EOF. В среде ОС UNIX диалог будет выглядеть следующим образом: % sketcher B 10 20 BBBBBBBBBBB Y 12 18 YYYYYYY [CTRL/-d] % /* художник-график */ /* РИСУЕТ сплошные фигуры */ #include #define MAXLENGTH 80 main( ) { int ch; /*печатаемый символ*/ int start, stop; /* начальная и конечные позици */ int count; /* счетчик позиций */ whilе((сh = getchar( )) != EOF) /* ввод символа */ { if(ch != '\n' ) /*пропуск символа "новая строка"*/ 135 { scanf(" %d %d", &start, &stop); /* ввод граничных значний*/ if (start > stop || start < 1 || stop > MAXLENGTH) printf(" Введены неправильные граничные значе ния \n"); else { count = 0; while(++count < start) putchar(' '); /* печать пробелов вплоть до начальной позиции */ while(count++ <= stop) putchar(ch); /*печать символа до конечной позиции */ putchar(' \n'); /* закончить печать строки и начать новую */ } /* конец части else */ } /* конец проверки содержимого ch */ } /* конец цикла while */ } /* конец программы */ РИС. 7. 5. Программа, рисующая символами Программа вывела на экран символ В в позициях с 10 по 20, а символ Y - с 12 по 18. К сожалению, при диалоговой работе с программой на экране наши команды перемежаются выводимым строками. Гораздо более удобным способом использования программы является создание файла, содержащего подходящий набор данных, а затем применение операции переключения для ввода (из него) параметров в программу. Предположим, например, что в файле с именем fig содержатся следующие данные: - 30 50 | 30 50 | 30 50 | 30 50 | 30 50 | 30 50 = 20 60 : 31 49 : 31 49 : 29 49 : 27 49 : 25 49 : 30 49 : 30 49 / 30 49 : 35 48 : 35 48 Послe ввода команды sketcher < fig результат работы программы будет выглядеть так, как показано на рис. 7.6. Внимание: на устройствах печати и экранах дисплеев отношение высоты символа к его ширине может быть различным, в результате символы фигуры при печати выглядят более сжатыми по вертикали, чем изображенные на экране). Анализ программы Далее Содержание Это короткая программа, но она оказалась сложнее тех примеров, которые мы обсуждали до сих пор. Рассмотрим некоторые ее элементы. Длина строки Далее Содержание Мы ввели в программу ограничение на длину печатаемой строки (она может быть больше 80 позиций), поскольку 80 символов - это стандартный формат (по ширине) экрана дисплеев различных типов, а также число символов при нормальной ширине листа бумаги на устройстве 136 печати. Вы можете, однако, переопределить величину константы MAXLENGTH, если при работе с программой захотите воспользоваться устройством, имеющим другую ширину строки. Структура программы Далее Содержание В нашей программе имеются три цикла while, один оператор if и один оператор if-else. Посмотрим, что каждый из них делает: while((ch = getchar()) != EOF) Задачей первого цикла while является ввод нескольких наборов данных. (Каждый набор данных состоит из символа и двух целых чисел, указывающих границы его вывода). Производя вначале чтение символа, мы смогли объединить в одном выражении его ввод и проверку того, не является ли он признаком EOF. Если прочитан символ EOF, то программа останавливается, не делая попытки ввести величины, соответствующие переменным start и stop. В противном случае при помощи функци scanf( ) указанным переменным присваиваются введенные значения, затем программа переходит к их обработке. Этим завершается выполнение тела цикла, после чего вводится новый символ, и весь процесс повторяется снова. Обратите внимание, что для чтения данных мы использовали два оператора, а не один. Почему нельзя было воспользоваться одним оператором? scanf(" %с %D %D", &ch, &start, &stop) Предположим, мы это сделали. Рассмотрим, что происходит, когда программа заканчивает чтение последнего набора данных из файла. Перед началом выполнения очередного тела цикла единственным оставшимся непрочитанным элементом файла будет признак EOF. Функция scanf( ) читает указанный символ и присваивает его переменной ch; зaтем oна пытается ввести значение для переменной start, но в файле не осталось данных, которые не были бы уже прочитаны! Компьютер выскажет свое недовольство, и ваша программа прекратит работу. Отделяя чтение символа от ввода остальных данных, мы позволяем компьютеру обнаружить появление признака EOF перед очередной попыткой прочесть оставшиеся данные. if (ch != '\n') Цель введения в программу первого оператора if состоит в том, чтобы упростить чтение данных. Мы объясним, как он работает в следующем разделе. if(start > stop || start < 1 || stop > MAXLENGTH) printf(" Введены неправильные граничные значения \n"); else Цель применения оператора if-else состоит в том, чтобы избежать использования в программе таких значении переменных start и stop, которые могут привести к нежелательным последствиям. Этот вопрос мы также обсудим ниже. Обратите, однако, внимание на то, как мы использовали логические операции и операции отношения, чтобы обнаружить появление любого из трех "опасных" значений. 137 Основная часть программы представляет собой составной оператор, который следует за ключевым словом else. count = 0; Вначале счетчик count устанавливается на нуль. while(++count < start) putchar(' '); Затем в цикле while начинается вывод на печать пробелов вплоть до позиции, определяемой значением переменной start. Ecли значение start, скажем, равно 10, то печатается девять пробелов. Поэтому вывод символов на печать начнется с 10-й позиции. Обратите внимание, как использование префиксной формы операции увеличения вместе с операцией < позволяет добиться указанного эффекта. Если бы вместо этого мы использовали выражение count++ < start, то сравнение проводилось бы перед увеличением значения count, и в результате мог быть напечатан один дополнительный пробел. while(count++ <= stop) putchar(ch); Второй цикл while в вышеупомянутом блоке осуществляет задачу вывода на печать символа, начиная с позиции, задаваемой переменной start, и кончая позицией, задаваемой переменной stop. На этот раз мы воспользовались постфиксной формой операции увеличения и операцией <=. Такая комбинация обеспечивает желаемый результат при выводе на печать символа - верхняя граничная позиция входит в поле печати. Для проверки этого факта вы можете воспользоваться логикой или методом проб и ошибок. putchar(' \n'); Оператор putchar('\n') используется для завершения печати данной строки и перехода на новую. Форма данных Далее Содержание При написании программы необходимо понимать, как она будет взаимодействовать с входными данными. Сейчас мы обратимся к этому вопросу. Вводимые данные должны быть представлены в форме, совместимой с требованиями, которые налагаются функциями ввода, используемыми в программе. Поэтому вся тяжесть приведения 138 данных к правильной форме ложится на пользователя. В более сложных программах основной объем работы по такому преобразованию переносится на саму программу. Наилучшей формой представления вводимых данных является следующая: Н 10 40 I 9 41 где за символом следуют номера начальной и конечной позиции. Но наша программа допускает также и такую форму данных: Н 10 40 I 9 41 и такую H10 40I 9 41 но не Н 10 40 I 9 41 Почему наличие одних пробелов является необязательным, а других - обязательным? Почему символ "новая строка" может быть помещен между последним целым числом из одного набора данных и первым символом из следующего набора, а пробел нет? Эти вопросы поднимают проблемы, касающиеся не только данной программы. Рассмотрим работу функций getchar() и putchar( ) и найдем ответы на них. Функция getchar( ) читает первый встретившийся символ независимо от того, является ли он алфавитным символом, пробелом, символом "новая строка" или еще чем-нибудь. Функция scanf() делает то же самое, если чтение производится в формате %с (символ). Но, когда scanf( ) осуществляет ввод данных в формате %d (целые), пробелы и символы "новая строка" пропускаются. Поэтому символы "новая строка" или любые пробелы между символом, считываемым функцией getchar( ), и следующим целым числом, считываемым функцией scanf( ), игнорируются. Функция scanf( ) читает цифры до тех пор, пока не встретит нецифровой символ - пробел, символ "новая строка" или букву. Следовательно, между первым и вторым целыми числами необходимо помещать пробел или символ "новая строка", чтобы функция scanf( ) могла распознать где кончается одно и начинается другое. Этим объясняется, почему между символом и следующим целым числом может стоять пробел или символ "новая строка", и почему между двумя целыми числами обязательно должен быть разделитель такого вида. Но почему между целым числом, стоящим в конце набора данных, и следующим символом не может быть пробел? Потому что в следующий раз на очередном шаге выполнения цикла while функция getchar( ) осуществляет ввод символа из той позиции, где "остановилась" функция scanf( ). Поэтому она прочтет любой следующий символ, стоящий после целого числа, - пробел, символ "новая строка" и т. п. Если бы мы следовали требованиям функции getchar( ), структуру данных необходимо было бы организовать так: w10 50a20 60у10 30 где между последним целым числом, стоящим в конце группы, и следующим символом разделитель отсутствует. Но такая структура выглядит неуклюже, поскольку число 50 при этом выглядит так, как будто оно помещено в одну группу с а, а не с w. Поэтому введен оператор if(ch != '\n') 139 чтобы иметь возможность обнаружить, когда значение ch равно символу "новая строка". Вместо этого можно использовать данные, вводимые в виде w 10 50 а20 60 у10 30 где между числом 50 и а помещен символ "новая строка". Программа читает этот символ, игнорирует его и затем переходит к чтению следующего символа. Контроль ошибок Далее Содержание Существует широко распространенная проблема, связанная с вводом в машину данных, которые должны использоваться определенным образом. Один из методов ее решения состоит в "контроле ошибок". Это означает, что, перед тем как приступить к обработке данных, программа должна проверить их правильность. В нашей программе мы сделали первую попытку осуществить такой контроль ошибок с помощью операторов: if(start > stop || start < 1 || stop > MAXLENGTH) printf(" Введены неправильные граничные значения. \n"); Они входят в структуру if-else, которая определяет, что основная часть программы будет выполняться только в том случае, если ни один из трех if-тестов не окажется истинным. С какой целью мы принимаем все эти меры предосторожности? Во-первых, совершенно неправильно размещать начальную позицию после конечной, поскольку обычно на терминал данные выводятся слева направо, а не наоборот. Поэтому с помощью выражения start > stop проверяется наличие такой потенциальной ошибки. Во-вторых, при выводе на экран первый столбец имеет номер 1; мы не можем выводить данные левее левого края. Выражение start < 1 служит средством обнаружения такой ошибки. И наконец с помощью выражения stop > MAXLENGTH проверяется, не пытаемся ли мы вывести на печать данные правее правого края. Существуют ли еще какие-нибудь ошибочные значения, которые мы можем присвоить переменным start и stop? Можно было бы, конечно, попробовать присвоить переменной start значение больше чем MAXLENGTH. Может ли этот вариант успешно пройти тест? Нет, хотя наличие подобной ошибки мы и не проверяем непосредственно. Предположим, что величина start больше константы MAXLENGTH. Тогда либо значение stop тоже превышает величину MAXLENGTH, что обязательно приведет к обнаружению ошибки, либо stop окажется меньшей или равной MAXLENGTH. Тогда ее значение должно быть меньше величины start, что приведет к обнаружению этой ошибки первым тестом. Другая вероятно ошибка может состоять в том, что значение stop окажется левее 1. Мы оставляем читателям в качeстве самостоятельного упражнения проверку того, что данная ошибка также не останется не замеченной. В нашей программе контроль ошибок выглядит весьма простым. Если вы проектируете программу для серьезных целей, вы должны обратить на этот больше внимания, чем мы. Например, выводимые сообщения об ошибках могли бы указывать какие величины неверны и почему; кроме того, вы могли бы прибавить в сообщения что-то от себя и придать им большую эмоциональную окраску. Приведем несколько примеров: Указаное вами значение stop - 897654 превышает ширину экрана. Вот это да! У вас START больше, чем STOP. Попробуйте, пожалуйста, еще раз. ВЕЛИЧИНА START ДОЛЖНА БЫТЬ БОЛЬШЕ 0, ИНДЮК. 140 Произносимые личностные моменты относятся и к вам. ОПЕРАЦИЯ УСЛОВИЯ: ?: Далее Содержание В языке Си имеется короткий способ записи одного из видов оператора if-else. Он называется "условным выражением" и использует операцию условия - ?:. Эта операция состоит из двух частей и содержит три операнда. Ниже приводится пример оператора с помощью которого находится абсолютное значение числа: x = (y < 0 )? -y : y; Все, что находится между знаком = и символом "точка с занятой" представляет собой условное выражение. Смысл этого оператора заключается в следующем: если у меньше 0, то х = - у; в противном случае х = у. В терминах оператора if-else данный оператор мог выглядеть так: if(у < 0) x = (y < 0 )? -y : y; х = -у; else х = у; В общем виде условное выражение можно записать следующим образом: выражение1 ? выражение2 : выражение3 Если выражение1 истинно (больше нуля), то значением всего условного выражения является величина выражения2; если выражение1 ложно (равно 0), то значение всего условного выражения - величина выражения3. Условное выражение удобно использовать в тех случаях, когда имеется некоторая переменная, которой можно присвоить одно из двух возможных значений. Типичным примером является присваивание переменной значения большей из двух величин: mах = (а > b)? а : b; Вообще говоря, использование условных выражений не являетcя обязательным, поскольку тех же результатов можно достичь при помощи операторов if-else. Однако условные выражения более компактны, и их применение обычно приводит к получению более компактного машинного кода. |