Проверка чисел с плавающей точкой и строк
Мы уже говорили, что в ветви case значение условия должно быть целым числом или символом. Поэтому нельзя написать инструкцию типа case 12.87: или case "Adam". Строки будут подробно рассматриваться в главе 10. Что касается значений типа float, то если вы хотите проверить такое значение, оно должно быть каким-то образом преобразовано в формат целого числа или символа.
В большинстве случаев для преобразования формата можно использовать конструкцию if...else. В Листинге8.8 приведен текст программы, в которой конструкция if...else присваивает целочисленное значение переменной level, основываясь на значении типа float, присвоенном переменной income.
Листинг 8.8. Использование вложенных инструкций if для преобразования чисел с плавающей точкой.
/*switch.c*/main() { float income; char level; printf("Введите сумму Вашего годового дохода: "); scanf("%f", &income); if(income<= 20000.00) level = '1'; else if(income<= 60000.00) level = '2'; else if(income<= 120000.00) level = '3'; else level = '4'; switch(level) { case '1': puts("Ставка налога = 15%"); break; case '2': puts("Ставка налога = 28%"); break; case '3': puts("Ставка налога = 32%"); break; case '4': puts("Ставка налога = 36%"); break; default: puts("Вы неправильно ввели сумму дохода"); } }На первый взгляд, кажется неразумным использовать несколько конструкций if...else и переключатель switch, в то время как можно было поместить все инструкции в if...else. Действительно, в простых программах, таких, как эта, можно без труда так и поступить, но когда инструкции, выполняемые в каждом случае, становятся более сложными, имеющими собственные вложенные инструкции if, использование переключателя switch является, несомненно, более удобным. Задание ветвей case позволит легко определить, сколько именно условий следует проверить и какие именно действия должны быть выполнены в каждом случае. Оформление процедуры присваивания значения с помощью серии последовательных конструкций if...else стоит той дополнительной работы, которую придется ради него проделать.
Проектирование программы
Как вы могли убедиться, существует несколько способов создания одной и той же программы: использование нескольких инструкций if, вложенных инструкций if, логических операторов или конструкции switch. В Листинге8.9 в качестве примера показано, как текст программы Опросника (в котором раньше использовался переключатель switch) теперь переделан с использованием вложенных инструкций if...else. В данном случае выбор одного из вариантов определяется вашим личным вкусом. Никто не может сказать, что ваш способ является ошибочным, до тех пор, пока ваша программа делает все, что от нее требуется. Разумеется, если в результате работы программы мы получаем неправильные результаты, значит в ней есть ошибочные инструкции, но при этом совершенно не обязательно, что ошибка была вызвана именно неправильным выбором структуры программы.
Листинг 8.9. Программа Опросника, в которой используются вложенные инструкции if...else.
/*quiz4.c*/main() { int answer; puts("Си это: \n"); puts("1. Язык, на котором говорят на юге Франции\n"); puts("2. Язык, который используется только для написания \больших компьютерных программ\n"); puts("3. Компилирующий язык, легко совместимый с любыми системами\n"); puts("4. Ничего из перечисленного\n"); puts("Введите номер ответа\n"); answer = getchar(); putchar('\n'); if(answer == '1') { puts("К сожалению, Вы ошиблись.\n"); puts("На юге Франции говорят на языке Паскаль\n"); } else if(answer == '2') { puts("Вы ошибаетесь, язык Си используют для написания программ\n"); puts("любых типов и размеров\n"); } else if(answer == '3') { puts("Очень хорошо, Вы дали правильный ответ\n"); puts("Си является компилирующим языком и может использоваться\n"); puts("с любыми компьютерными системами\n"); } else if(answer == 4) puts("К сожалению, Вы ошибаетесь, правильный ответ - номер 3\n"); else puts("Вы ввели символ, не соответствующий номеру ответа\n"); }Выбирайте тот метод, который кажется вам более удобным и позволяет написать более стройную программу без использования излишне усложненных конструкций. Имейте в виду, часто бывает так, что лучший метод требует использования большего количества инструкций, а самая короткая программа не всегда является самой лучшей. И не забывайте о том, что какой бы метод вы ни выбрали для создания программы, всегда следует проверять правильность ввода.
Проверка правильности ввода
Не очень надейтесь на то, что пользователь всегда будет вводить правильное число или символ, даже если можно использовать и прописные и строчные буквы. Например, в главе 7 мы приводили текст программы, в которой требовалось ввести процент скидки в виде десятичной дроби. Все расчеты могут оказаться неверными, если ошибочно ввести 5 вместо 0.05 для пятипроцентной скидки. Одним из способов решения этой проблемы является введение добавочной инструкции:
if (mrkdown >1) mrkdown = mrkdown / 100;
В главе 6 вы видели программу расчета оплаты труда. Если помните, там возникла ситуация, когда программа вычитала из заработной платы сотрудника оплату в двойном размере за каждый час, недостающий до 40-часовой рабочей недели. Программа, в которой этот недостаток исправлен, приведена в Листинге8.10. Инструкция if включает два набора инструкций: один для случая, когда количество отработанных за неделю часов было не меньше сорока, и второй для случая, когда количество отработанных часов было меньше 40. Значение любой переменной, которая будет отображаться на экране, присваивается в результате выполнения того или иного набора инструкций, так что никаких случайных величин на экране не появится.
Листинг 8.10. Исправленная программа расчета заработной платы.
/*allhours.c*/main() { float rate, hours, total, regular, extra, d_time, overtime; printf("Введите оплату одного часа работы: "); scanf("%f",&rate); printf("Введите число отработанных часов: "); scanf("%f", &hours); d_time = rate * 2; if(hours <= 40) { regular = hours * rate; extra = 0.0; overtime = 0.0; total = regular; } else { regular = 40 * rate; extra = hours -40; overtime = extra * d_time; total = regular + overtime; } printf("Нормальный недельный заработок: %.2f\n", regular); printf("Отработано сверхурочных часов : %.2f\n", extra); printf("Средняя часовая оплата сверхурочных: %.2f\n", d_time); printf("Заработок за сверхурочные часы: %f.2\n", overtime); printf("Общая сумма недельного заработка: %f.2\n", total); }Значение оплаты сверхурочных (удвоенное значение оплаты одного часа) присваивается переменной d_time. Расчет этого значения производится перед инструкцией if, так как каждый работник имеет определенную ставку оплаты сверхурочных часов, даже если он реально и не работал больше нормы за истекший период. Соответственно, поскольку сам по себе расчет не зависит от выполнения какого-либо условия, для него не используется инструкция if.
Однако и теперь в программе не разрешены все проблемы, которые могут возникнуть. Например, вы можете ввести отрицательное значение почасовой оплаты или указать какое-нибудь несуразное количество отработанных часов (скажем, 2500 в неделю). Как окончательно решить все проблемы, возникающие из-за ошибочного ввода, вы узнаете в следующей главе.
<> | Вопросы |
|
<> | Упражнения |
|
ГЛАВА 9. ЦИКЛЫ
Программа начинает и заканчивает свою работу, но это не значит, что каждая инструкция в программе должна выполняться только однажды. Вы можете решить провести несколько циклов обработки данных или выполнить несколько различных вычислений. Вы можете также попросить пользователя вводить данные до тех пор, пока он не сделает это надлежащим образом.
Язык Си и Си++ имеет три структуры, известные под названием циклов, которые используются для управления повторами:
цикл for; цикл do...while; цикл while.Любой из этих циклов может быть применен для повторения инструкции, группы инструкций или даже целой программы.
Использование цикла for
Цикл for используется в том случае, когда известно точное количество повторов, которое нужно выполнить. Структура такого цикла показана на рис.9.1. Обратите внимание на то, что точка с запятой ставится только после инструкции, а не после параметров for. Однако три параметра внутри круглых скобок отделяются друг от друга точкой с запятой.
В приведенной ниже программе цикл for используется для того, чтобы вывести на экран монитора числа от 1 до 10, расположенные друг под другом.
main() { int repeat; for (repeat = 1; repeat <= 10; repeat++) printf("%d\n", repeat); }Этот цикл управляется переменной repeat, которая называется индексом*. Индексу можно присвоить любое имя, но значение переменной обязательно должно быть целым числом. Выражение в круглых скобках после for делится на три составляющие:
repeat=1 | инициализация переменной repeat путем присваивания ей начального значения |
repeat <= 10 | задает условие повтора цикла до тех пор, пока значение переменной repeat остается меньше или равно 10 |
repeat++ | приращение значения переменной repeat после каждого повтора цикла |
_________________________
* Иногда используется термин управляющая переменная цикла. (Прим.перев.)
Рис. 9.1. Структура цикла for
|
При каждом новом повторе цикла программа выводит на экран текущее значение переменной repeat.
Когда программа начнет выполнение цикла, она присвоит переменной repeat начальное значение, равное 1. Затем будет проверено, является ли истинным условие, что значение переменной меньше или равно 10. Если условие истинное, начнется выполнение инструкции, связанной с циклом, то есть вывод на экран значения переменной.
После выполнения инструкции произойдет увеличение значения переменной на единицу и снова будет проведена проверка истинности условия (рис.9.2). Так как условие все еще является истинным, цикл будет выполнен во второй раз, отображая на дисплее текущее значение переменной. Этот процесс будет
Рис. 9.2. Условие проверяется перед каждым повтором цикла
повторяться до тех пор, пока значение переменной не вырастет до 11. Как только это произойдет, условие repeat <= 10 уже не будет истинным, так что выполнение инструкции прекратится и цикл завершится.
В предыдущем примере значение индекса использовалось непосредственно в инструкции вывода. В то же время можно написать инструкции следующим образом:
main() { int repeat; char letter; puts("Введите 10 символов"); for(repeat = 1; repeat <= 10; repeat++) letter = getchar(); }В этой программе функция getchar() выполняется 10 раз, по количеству повторов цикла, пока значение переменной repeat не увеличится с 1 до 11. Индекс в данном случае используется только для определения количества повторов. С тем же результатом можно было записать инструкции следующим образом:
for (repeat = 101; repeat <= 110; repeat++) letter = getchar(); }Здесь также вводится 10 символов, но теперь значение индекса изменяется от 101 до 110. Точное значение индекса приобретает значение только в том случае, когда оно само по себе используется в цикле.
Создание паузы в программе
Цикл for можно использовать и без инструкций, с целью создания задержки в программе:
for (delay = 1; delay <= 1000; delay++);Хотя инструкции, связанные с циклом, отсутствуют, тем не менее, цикл будет повторен 1000 раз, пока выполняется указанное условие, то есть пока значение переменной delay не возрастет с 1 до 1001. Выполнение повторов цикла приостановит переход программы к выполнению следующих инструкций.
Одним из возможных применений такого цикла является временная остановка вывода на экран сообщений, с тем, чтобы дать пользователю время прочитать инструкции или подсказки. Этот способ можно использовать наряду с тем, где пользователю предлагается нажать Enter для продолжения вывода сообщений.
Вы можете провести эксперимент, чтобы выяснить, как долго будет выполнять тысячу повторов ваша система. Длительность паузы зависит от быстродействия компьютера. Выполнение цикла может длиться 1 секунду на быстром компьютере и 2 секунды на медленном*. Чтобы установить желательную длительность паузы, можно увеличивать или уменьшать количество повторов, заданное в условии.
___________________________
* Автор несколько утрирует. Для получения паузы в 1 секунду даже на очень медленном компьютере цикл потребуется повторить несколько тысяч раз. (Прим.перев.
Составные инструкции
В одном цикле можно выполнить несколько инструкций. Для этого всю последовательность инструкций, относящихся к циклу, следует заключить в фигурные скобки. В качестве примера посмотрите программу перевода 101 последовательного значения температур (от 32 до 132) в значения по шкале Цельсия:
main() { int temp; float celsius; puts("Шкала Фаренгейта\tШкала Цельсия\n"); for (temp = 32; temp <= 132; temp++) { celsius = (5.0 / 9.0) * (temp - 32); printf("%d\t\t%6.2f\n", temp, celsius); } }
|
При каждом повторе цикла выполняются две инструкции. Значение индекса определяет как количество повторов, так и значения, которые следует перевести в шкалу Цельсия. Сравните только что просмотренную вами программу со следующей:
main() { int temp, repeat; float celsius; puts("Шкала Фаренгейта\tШкала Цельсия\n"); temp = 10; for (repeat = 1; repeat <= 10; repeat++) { celsius = (5.0 / 9.0) * (temp - 32); printf("%d\t\t%6.2f\n", temp, celsius); temp += 10; } }Теперь индекс определяет только количество повторов. Значения температуры, которые следует преобразовать, определяет переменная temp. Эта переменная увеличивает свое значение на 10 при каждом повторе: с 10 до 20, 30 и так далее, вплоть до 100.
Использование переменных
Если во время составления программы вы не знаете, сколько повторов потребуется, вы все же можете использовать цикл for, при условии, что количество повторов будет указано в момент запуска программы на выполнение. Можно ввести значение в переменную и использовать ее в условии. Например, следующая программа просит пользователя указать пределы значений температуры, которые требуется преобразовать, то есть, по сути, пользователь сам должен определить количество повторов цикла:
main() { int temp, start, end; float celsius; printf("Введите начальное значение температуры: "); scanf("%d", &start); printf("Введите конечное значение температуры: "); scanf("%d", &end); puts("Шкала Фаренгейта\tШкала Цельсия\n"); for (temp = start; temp <= end; temp++) { celsius = (5.0 / 9.0) * (temp - 32); printf("%d\t\t%6.2f\n", temp, celsius); } }Здесь требуется ввести начальное и конечное значения температур, которые мы хотим привести к шкале Цельсия. Переменные start и end используются в цикле for для установки начального значения индекса и для проверки условия. Цикл завершится, когда значение переменной temp превысит величину переменной end. Таким образом, если вы введете числа 20 и 43, программа преобразует значения температур от 20 до 43 градусов по Фаренгейту в соответствующие значения по шкале Цельсия. Цикл будет повторен 24 раза, затем завершится.
Вложенные циклы
Если один цикл for выполняется внутри другого, принято говорить, что мы имеем дело с вложенным циклом. Внутренний цикл целиком выполняется во время каждого повторения внешнего цикла. Вложенные циклы for можно представить себе как двухмерные, а единичный — как одномерный.
В качестве иллюстрации рассмотрим следующую программу:
main() { int row, column; for (row = 1; row <= 10; row++) { for (column = 1; column <= 10; column++) printf("*"); putchar('\n'); /*вне внутреннего цикла, но внутри внешнего*/ } }Программа выводит на экран монитора 10 рядов, состоящих из 10 звездочек. Здесь используются две целочисленные переменные row и column. Во внешнем цикле переменная row увеличивает свое значение на единицу при каждом повторе. Кроме того, при каждом повторе внешнего цикла, внутренний цикл повторяется 10 раз, увеличивая значение переменной column и выводя на экран
Рис. 9.3. Внешний и внутренний циклы
ряд из десяти звездочек (обратите внимание, что имена переменным даны с таким расчетом, чтобы пояснить логику программы: row по-английски значит «строка», а column — «столбец» или «колонка»). На рис.9.3 продемонстрирована работа этих вложенных циклов. К инструкциям внутреннего цикла относится
for (column = 1; column <= 10; column++)printf("*");
Рис. 9.4. Значения, которые имеют переменные во время каждого повтора цикла
В результате работы программы на экране появятся 100 звездочек: 10 внутренних циклов сформируют 10 колонок, а 10 внешних циклов — 10 рядов. Значения, которые переменные приобретают при каждом повторе цикла, показаны на рис.9.4.
Обратите внимание на положение инструкции putchar('\n');. Она не относится к внутреннему циклу, но в то же время находится внутри фигурных скобок, ограничивающих внешний цикл. Эта инструкция выполняется десять раз, по одному на каждый повтор внешнего цикла, вставляя код «новая строка» после каждого ряда звездочек.
В Листинге 9.1 приведен другой пример использования вложенных циклов. Десять внешних и десять внутренних циклов здесь используются для того, чтобы создать таблицу умножения. Вместо того чтобы просто выводить на экран ряды звездочек, программа выводит результаты произведения значения переменной row на значение переменной column.
Листинг 9.1. Программа создания таблицы умножения.
/*timestab.c*/main() { int row, column; puts("\t\tТаблица Пифагора\n\n"); for(row = 1; row <= 10; row++) { for(column = 1; column <= 10; column++) printf("%6d", row * column); putchar('\n'); } }
Рис. 9.5. Результат работы программы, создающей таблицу умножения
Результат работы программы, названной нами TIMESTAB.C, изображен на рис.9.5.
Наконец, рассмотрим следующую программу:
main() { int row, column; for (row = 1; row <= 10; row++) { for (column = 1; column <= row; column++) printf("*"); putchar('\n'); } }Она выводит на экран последовательность звездочек, показанную на рис.9.6.
Рис. 9.6. Количество повторов внутреннего циклаопределяется номером повтора внешнего цикла
Мы видим, что каждый ряд звездочек имеет разную длину. Это обусловлено тем, что количество повторов внутреннего цикла не одинаково, а возрастает с каждым следующим выполнением внешнего цикла: одна звездочка в первом ряду, две звездочки во втором ряду, три в третьем и так далее. Количество колонок совпадает с номером ряда. Мы добились такого эффекта, используя переменную row в качестве условия внутреннего цикла.
При первом выполнении внешнего цикла внутренний цикл выполняется только один раз, выводя на экран одну звездочку. При втором повторе внешнего цикла внутренний цикл выполняется два раза, выводя две звездочки. В результате продолжения этого процесса получается узор из звездочек.
Будьте внимательны, когда пишете программу, содержащую два и больше вложенных циклов for. Если в программе указано 100 повторов внешнего и 100 повторов внутреннего цикла, это означает, что потребуется выполнить 10 тысяч повторов!
Использование цикла do...while
Цикл do...while используется в тех случаях, когда вы не знаете точного количества повторов, но в то же время вам известно, что цикл необходимо выполнить, по меньшей мере, один раз. Структура цикла do...while приведена на рис.9.7.
Выполнение инструкций, заключенных в фигурные скобки, повторяется до тех пор, пока является истинным условие, указанное в while. Правильность условия проверяется только в конце цикла, так что, даже если условие с самого начала не было истинным, цикл будет выполнен, по меньшей мере, один раз.
Рис. 9.7. Структура цикла do...while
Используя цикл do...while, следует указывать условие так, чтобы выполнение его не оказалось бесконечным. Ведь если условие все время будет выполняться, остановить повторение цикла можно будет только перезагрузкой системы с помощью кнопки Reset или комбинации клавиш Ctrl+Alt+Del.
Цикл do...while часто используется для того, чтобы повторять программу до тех пор, пока пользователь не решит закончить ввод:
main() { int temp; float celsius; char repeat; do { printf("Введите значение температуры: "); scanf("%d", &temp); celsius = (5.0 / 9.0) * (temp - 32); printf("%d градусов по Фаренгейту соответствует %6.2f по Цельсию\n", temp, celsius); printf("Желаете ввести еще значение?"); repeat = getchar(); putchar('\n'); } while (repeat == 'y' || repeat == 'Y'); }
|
В этом примере практически весь текст программы, исключая определение переменных, входит в большой блок do...while. Цикл будет повторяться до тех пор, пока в ответ на запрос будет вводиться символ Y или y. Обратите внимание, что инструкция, выводящая на экран запрос о продолжении работы, является последней инструкцией в блоке.
Цикл do...while также используют и для того, чтобы обеспечить правильность ввода данных. Например, у нас есть программа, в которой требуется ввести процент скидки стоимости товара в виде десятичной дроби. Если пользователь вводит значение, не соответствующее формату, например, меньше 0 либо больше или равно 1, можно, с помощью цикла do...while, предложить ему повторить ввод, написав инструкции примерно следующего содержания:
do { printf("Введите размер скидки:"); scanf("%f", &discount); }while (discount < 0 || discount >= 1);Условие, записанное в while с использованием логического оператора ИЛИ, проверяет, находится ли введенное число в определенных допустимых границах. Цикл будет повторяться до тех пор, пока пользователь не введет данные надлежащим образом. Но тут есть одна тонкость.
Описанная процедура может дать хорошие результаты только в том случае, если пользователь в конце концов поймет, что от него требуется, и введет правильное число. Если же вы имеете дело с человеком, который не имеет представления о том, что такое десятичная дробь, цикл может повторяться до бесконечности. Одним из способов справиться с такой ситуацией является использование алгоритма счетчика. Например, можно написать следующую программу:
main() { int count; float discount; count=0; do { printf("Введите размер скидки: "); scanf("%f", &discount); count++; } while ((discount < 0 || discount >= 1) && count < 20); if (count == 20) puts ("Дурак"); }Теперь у пользователя есть 20 попыток, чтобы ввести правильные данные. Значение переменной count увеличивается на единицу после каждой неправильной попытки.
Для того чтобы выполнить только одну инструкцию в цикле do...while, нет необходимости использовать фигурные скобки. Например, следующая программа позволяет вводить символы, пока не будет нажата клавиша Y:
main() { int a; do a = getchar(); while (a != 'y' && a != 'Y'); }
Рис. 9.8. Вложенные циклы do...while
Вложенные циклы do
Вложенные циклы do...while можно использовать для того, чтобы обеспечить несколько уровней повторов. Можно, например, использовать внешний цикл, чтобы повторять всю программу, пока пользователь не решит заняться чем-нибудь другим, и одновременно внутренний цикл для проверки правильности ввода, как это показано на рис.9.8. Внутренний цикл используют для ввода чисел, значения которых должны изменяться в промежутке от 0 до 100. Если ввод данных соответствует этому условию, внутренний цикл завершается и выполняются инструкции внешнего цикла. Выполнение внешнего цикла повторяется, пока является истинным условие
while (repeat == 'y' || repeat == 'Y');Как только пользователь вводит какой-нибудь другой символ, внешний цикл завершается, приводя тем самым к завершению всей программы.
Использование цикла while
Цикл while используется в том случае, когда не известно точное число повторов и при этом нет необходимости, чтобы цикл непременно был выполнен хотя бы один раз. Структура цикла while такова:
while (condition)instruction;Синтаксис показан на рис.9.9. Составные инструкции записываются следующим образом:
while (condition) { instructions; }
Рис. 9.9. Структура цикла while
Так же, как и цикл do, цикл while выполняется до тех пор, пока является истинным условие, но в отличие от конструкции do...while, условие проверяется до начала выполнения цикла, даже если цикл выполняется первый раз. Если условие окажется ложным, цикл не будет выполнен ни разу.
Используя цикл для проверки правильности ввода, помещайте первую инструкцию ввода вне цикла. После этого можно продолжать запрос о вводе данных до тех пор, пока не будет введено правильное значение. Например, таким образом:Так же, как и цикл do, цикл while выполняется до тех пор, пока является истинным условие, но в отличие от конструкции do...while, условие проверяется до начала выполнения цикла, даже если цикл выполняется первый раз. Если условие окажется ложным, цикл не будет выполнен ни разу.
printf("Введите размер скидки: ");scanf("%f", &discount);while (discount < 0 || discount >= 1) { printf("Вы ввели неправильное значение"); scanf("%f", &discount); }