Главная страница

Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин


Скачать 4.69 Mb.
НазваниеM. уэит с. Прата д. Мартин
АнкорЯзык Си - Уэйт, Прата, Мартин.pdf
Дата15.03.2018
Размер4.69 Mb.
Формат файлаpdf
Имя файлаЯзык Си - Уэйт, Прата, Мартин.pdf
ТипПрограмма
#16711
страница20 из 42
1   ...   16   17   18   19   20   21   22   23   ...   42
while записывается так:
while(
выражение
) оператор
В наших примерах в качестве выражении использовались условные выражения, но, вообще говоря, это могут быть выражения произвольного типа. В качестве оператора можно использовать простой оператор с символом "точка с запятой" в конце или составной oпeратор, заключенный в фигурные скобки. Если выражение истинно (т.е в общем случае не равно нулю), то оператор,
входящий в цикл while выполняется один раз, а затем выражение проверяется снова, а последовательность действий, состоящая из проверки и выполнения оператора, периодически повторяется до тех пор, пока выражение не станет ложным (или в общем случае равным нулю).
Такой шаг называется "итерация". Данная структура аналогична структуре оператора if. Основное отличие заключается в том, что в операторе if проверка условия и (возможное) выполнение оператора осуществляется только один раз, а в цикле while эти действия производятся, вообще говоря, неоднократно.
148

РИС. 8.1. Структура цикла while.
Завершение цикла while
Далее
Содержание
Мы подошли к самому существенному моменту рассмотрения циклов while. При построении цикла while вы должны включить в него какие-то конструкции, изменяющие величину проверяемого выражения так, чтобы в конце концов оно стало ложным. В противном случае выполнение цикла никогда не завершится. Рассмотрим следующий пример:
index = 1;
while(index < 5)
printf("Доброе утро!\n");
Данный фрагмент программы печатает это радостное сообщение бесконечное число раз,
поскольку в цикле отсутствуют конструкции, изменяющие величину переменной index, которой было присвоено значение 1.
index = 1;
while(--index < 5)
printf("Как колеблются старые атомы!\n");
И этот фрагмент программы работает ненамного лучше. Значение переменной index в нем изменяется, но в "неправильном" направлении! Единственным утешением здесь служит тот факт,
что выполнение данного куска программы в конце концов завершится. Это произойдет, когда величина переменной index станет меньше наименьшего отрицательного числа, допустимого в системе.
Цикл while является "условным" циклом, использующим предусловие (т.е. условие на входе). Он называется условным, потому что выполнение оператора зависит от истинности условия,
описываемого с помощью выражения. Действительно ли значение переменной index меньше 5?
Является ли последний введенный символ признаком EOF? Подобное выражение задает предусловие, поскольку выполнение этого условия должно быть проверено перед началом выполнения тела цикла. В ситуации, аналогичной приведенной ниже, тело цикла не выполнится ни разу, потому что используемое условие с самого начала является ложным.
index = 10;
while(index++ < 5)
printf(" Желаю хорошо провести день.\n");
Измените первую строку на index = 3;
149
и вы получите работающую программу.
АЛГОРИТМЫ И ПСЕВДОКОД
Далее
Содержание
А теперь вернемся к нашей "тупоумной" программе, угадывающей число. Недостаток этой программы кроется не в программировании самом по себе, а в "алгоритме", т.е. методе,
используемом для отгадывания числа. Этот метод можно описать следующим образом: попросите пользователя задумать число компьютер начинает угадывание с 1 до тех пор пока догадка неверна,
предлагаемое значение увеличивается на 1.
Эта запись, между прочим, служит примером "псевдокода" представляющего собой способ выражения смысла программ на разговорном языке и являющегося некоторым аналогом языка машины. Псевдокод очень эффективен при разработке логики программы. После того как логика покажется вам правильной, вы можете обратить основное внимание на детали перевода псевдокода на реальный язык программирования. Преимущество использования псевдокода состоит в том, что он позволяет сконцентрироваться на логике и структуре программы, не заботясь пока о способе перевода этих идей на язык машины. Если мы хотим улучшить программу, нам в первую очередь необходимо улучшить алгоритм. Один из методов заключается в том, чтобы выбрать число где-нибудь посередине между 1 и 100 (50 нам вполне подходит) и попросить пользователя ответить больше ли это число задуманного, меньше его или равно ему. Если он сообщает, что данное число слишком велико, то тем самым из рассмотрения немедленно исключаются все числа между 50 и 100. Следующей догадкой программы является число,
выбранное где-то посередине между 1 и 49. И снова ответ на вопрос, велико или мало это число,
позволит исключить из рассмотрения половину оставшихся возможных чисел; программа продолжает указанный процесс, быстро сужая поле поиска до тех пор, пока задуманное число не будет угадано. Давайте запишем эти логические рассужденя на псевдокоде. Пусть highest - максимально возможная величина отгадываемого числа, a lowest - его минимально возможное значение. Вначале этими величинами будут соответственно 100 и 1, поэтому алгоритм запишется следующим образом:
установить highest равным 100
установить lowest равным 1
попросить пользователя задумать число предложенное значение (guess) равно (highest + lowest)/2
пока догадка неверна, делать следующее:
{если предложенное значение велико, установить highest равным этому предложенному значению минус 1
если предложенное значение мало, установить lowest равным этому предложенному значению плюс 1
новое предложенное значение равно (highest + lowest)/2 }
Обратите внимание на логику алгоритма: если предложенное значение, равное 50, велико, то максимально возможная величина задуманного числа будет равна 49. Если же значение 50 мало, то минимально возможная величина числа будет равна 51.
Сейчас мы переведем текст, указанный выше, на язык Си. Полученная программа представлена на рис. 8.2.
/* угадывание числа2 */
/* более эффективный способ угадывания*/
#include
#define HIGH 100
#define LOW 1
main( )
{
int guess = (HIGH + LOW)/2;
int highest = HIGH;
int lowest = LOW;
char response;
150
printf(" Задумайте число от %d до %d. Я попробую", LOW, HIGH);
printf(" угадать eгo.\n Отвечайте д, если моя догадка правильна,");
printf(" б, если \n больше, и м, если");
printf(" меньше.\n");
printf(" Итак ... ваше число %d?\n" , guess);
while((response = getchar( )) != 'д')
{ if( response != '\n')
{
if (response == 'б')
{ /* уменьшение верхнего предела,
eсли предложенное значение слишком велико */
highest = guess - 1;
guess = (highest + lowest)/2;
printf(" Гм ... слишком велико. Ваше число %d?\n", guess); }
else if(response == 'м')
{ /* увеличение нижнего предела,
если предложенное значение слишком мало*/
lowest = guess + 1;
guess = (highest + lowest)/2;
printf(" Гм ... слишком мало. Ваше число %d?\n" , guess); }
else
{ /* подводите пользователя к правильному ответу */
printf(" Я не понимаю; введите, пожалуйста, д,б");
printf ("или м.\n");
} }
printf("Я знала, что смогу сделать это!\n"); }
РИС. 8.2. Программа, угадывающая число.
Наличие в программе завершающей части else предоставляет пользователю дополнительную возможность правильно ответить на стандартный "отклик" программы. Заметим также, что мы использовали символические константы, чтобы сделать процесс изменения диапазона чисел достаточно простым. Работает ли данная программа? Ниже приводятся результаты этого прогона.
Задуманное число - 71.
Задумайте число от 1 до 100. Я попробую угадать eгo
Отвечайте д, если моя догадка правильна б, если больше, и м, если меньше.
Итак ..., ваше число 50?
Я не понимаю: введите, пожалуйста, д,б или м.
м
Гм ... слишком мало. Ваше число 75?
б
Гм ... слишком велико. Ваше число 62?
м
Гм ... слишком мало. Ваше число 68?
м
Гм ... слишком мало. Ваше число 71?
д
Я знала, что смогу сделать это!
Что может быть неправильного в этой программе? Мы реализовали в ней защиту от ошибок,
вызванных тем, что пользователи могут указывать неверные символы, поэтому здесь не должно быть никаких проблем. Единственное, что может повлиять на правильность работы программы:
если вы вместо м укажете б, или наоборот. К сожалению, не существует способа заставить пользователя говорить правду и не делать ошибок. Тем не менее, если вы заинтересованы в этом,
можете предпринять некоторые шаги. (Например, если захотите поразить свою шестилетнюю племянницу.) Во-первых, обратите внимание на то, что наш способ требует самое большее семи попыток для угадывания любого числа. (Каждая попытка уменьшает число возможностей наполовину. За семь попыток можно угадать любое число в диапазоне от 1 до 27- 1, или 127, что вполне достаточно для работы в диапазоне или 1 до 100.) Вы можете модифицировать программу так, чтобы она подсчитывала число попыток, и если окажется, что оно превышает 7, то тогда можно
151
вывести на печать сообщение с выражением недовольства, а затем восстановить первоначальные значения переменных highest, lowest и счетчика. Дополнительные изменения, которые можно внести в программу, заключаются в такой модификации операторов if, в результате которой допускался бы ввод как прописных, так и строчных букв.
Резюме: оператор while
Оператор while определяет операции, которые циклически выполняются до тех пор, пока проверяемое выражение не станет ложным, или равным нулю. Оператор while - это цикл с предусловием; решение, выполнять ли в очередной раз тело цикла, принимается перед началом его прохождения. Поэтому вполне возможно, тело цикла не будет выполнено ни разу. Оператор,
образующий тело цикла, может быть либо простым, либо составным.
while(
выражение
) оператор
Выполнение оператора циклически повторяется до тех пор, пока выражение не станет ложным,
или равным нулю.
ПРИМЕРЫ
while(n++ < 100) printf(" %d %d \n",n, 2*n + 1);
while(fargo < 1000)
{ fargo = fargo + step;
step = 2 * step;
}
В нашем последнем примере в цикле while используется "неопределенное " условие: мы не знаем заранее, сколько раз выполнится тело цикла перед тем, как выражение станет ложным. Во многих наших предыдущих примерах, однако, циклы while использовались для подсчета числа выполнении тела цикла. Ниже приведен краткий пример
1)
, который содержит подсчитывающий цикл main( )
{ int = 1; /* инициализация */ while (count <= NUMBER) /* проверка */
{ printf(" Будь моим Валентином !\n"); /*действие */
count++; /* увеличение счетчика */
}
}
Хотя цикл подобного типа прекрасно работает, это не лучший вариант его записи, поскольку операции, реализующие цикл, не собраны вместе. Рассмотрим этот вопрос более подробно. При организации цикла, когда его тело должно быть выполнено фиксиррованное число раз,
осуществляются три операции: инициализация счетчика, сравнение его величины с некоторым граничным значением и увеличение значения счетчика при каждом прохождении тела цикла.
Условное выражение, имеющееся в цикле while, берет на себя заботу о сравнении, а приращение значения счетчика осуществляется с помощью операции увеличения. Так же как это делалось раньше, можно объединить эти два действия в одно выражение, используя запись count++ <=
NUMBER. Но инициализация счетчика осуществляется вне цикла, как, например, в нашем примере оператором count = 1;. При этом можно забыть о необходимости инициализации счетчика, а то, что может случиться, рано или поздно случается. Сейчас мы рассмотрим управляющий оператор,
использование которого позволяет избежать этих проблем.
ЦИКЛ for
Далее
Содержание
152

В цикле for все три вышеуказанных действия собраны вместе. Используя цикл for, фрагмент,
приведенный выше, можно записать в виде одного оператора:
for(count = 1; count <= NUMBER; count++)
printf(" Будь моим Валентином! \n ");
В круглых скобках содержатся три выражения, разделенные символом "точка с запятой". Первое из них служит для инициализации счетчика. Она осуществляется только один раз - когда цикл for
начинает выполняться. Второе выражение - для проверки условия; она производится перед каждым возможным выполнением тела цикла. Когда выражение становится ложным (или в общем случае равным нулю), цикл завершается. Третье выражение вычисляется в конце каждого выполнения тела цикла. Ранее мы использовали его для увеличения значения счетчика count, но, вообще говоря, его использование этим не ограничивается. За заголовком цикла for следует простой или составной оператор. Рис. 8.3 служит иллюстрацией структуры цикла for.
РИС. 8.3. Структура цикла for.
Cейчас мы продемонстрируем, как цикл for используется в программе, печатающей таблицу кубов целых чисел:
/* таблица кубов */
main( )
{ int num;
for(num=1;num<=6;num++)
printf(" %5d %5d \n", num, num*num*num);
}
программа выводит на печать числа от 1 до 6 и их кубы:
1 8
27 64 125 216
Из первой строки цикла for мы сразу можем узнать всю информацию о параметрах цикла:
начальное значание переменной num, конечное значение, а также насколько увеличивается значение переменной num при каждом выполнении тела цикла. Цикл for часто используется для реализации в программе временной задержки с целью согласования скорости реагирования (в
153
даном случае замедления) машины с возможностями восприятия человека.
for(n=1; n<= 10000; n++)
;
Этот цикл заставляет машину считать до 10000. Единственный символ "точка с запятой",
расположенный во второй строке, интересует нас о том, что никаких других действий в этом цикле не производится. Такой уединенный символ "точка с запятой" можно представлять себе как "пустой"
оператор, т. е. оператор, который не выполняет никаких действий.
Гибкость конструкции for
Далее
Содержание
Хотя цикл for на первый взгляд очень похож на цикл DO в Фортране, цикл FOR в Паскале и цикл
FOR ... NEXT в Бейсике, for в Си является гораздо более гибким средством, чем любой из упомянутых. Эта гибкость - следствие способа использования упомянутых выше трех выражений в спецификации цикла for. До сих пор первое выражение применялось для инициализации счетчика,
второе - для задания его граничного значения, а третье - для увеличения его текущего значения на
1. Использованный таким образом оператор for в языке Си совершенно аналогичен упомянутым выше соответствующим операторам в других языках. Но, кроме описанной, существует еще и много других возможностей его применения, девять из которых мы приводим ниже.
1. Можно применять операцию уменьшения для счета в порядке убывания вместо счета в порядке возрастания:
for(n = 10; n > 0; n--)
printf(" %d секунд!\n", n);
printf(" Пуск!\n");
2. При желании вы можете вести счет двойками, десятками и т. д.
for (n = 2; n & 60; n = n + 13) printf(" %d\n", n);
В этом операторе значение переменной n будет увеличиваться на 13 при каждом выполнении тела цикла; будут напечатаны числа 2, 15, 28, 41 и 54.
Заметим, между прочим, что в языке Си имеется и другая сокращенная форма записи для увеличения переменной на фиксированную величину. Вместо выражения n = n + 13 можно воспользоваться записью n + = 13.
Знак += определяет "аддитивную операцию присваивания", в результате выполнения которой величина, стоящая справа, прибавляется к значению переменной, расположенной слева.
Дополнительные детали, относящиеся к этой операции, приведены ниже.
3. Можно вести подсчет с помощью символов, а не только чисел.
for(ch = 'а' ; ch <= 'z'; ch++)
printf(" Величина кода ASCII для %с равна %d.\n" , ch, ch);
При выполнении этого оператора будут выведены на печать все буквы от а до z вместе с их кодами
ASCII. Этот оператор "работает", поскольку символы в памяти машины размещаются в виде чисел и поэтому в данном фрагменте счет ведется на самом деле с использованием целых чисел.
4.Можно проверить выполнение некоторого произвольного условия, отличного от условия,
налагаемого на число итераций. В нашей программе таблица кубов вы могли бы заменить спецификацию for(num = 1; num <= 6; num ++)
154
на for(num = 1; num *num *num <= 216; num++)
Это было бы целесообразно в случае, если бы нас больше занимало ограничение максимального значения диапазона кубов чисел, а не количества итераций.
5. Можно сделать так, чтобы значение некоторой величины возрастало в геометрической, а не в арифметической прогрессии, т. е. вместо прибавления фиксированного значения на каждом шаге цикла, выполнялось бы умножение:
for(debt = 100.0; debt < 150.0; debt = debt*l.l)
printf(" Baш долг теперь $%.2f.\n", debt);
В этом фрагменте программы значение переменной debt умножается на 1.1 на каждом шаге цикла,
что увеличивает ее на 10%. Результат выглядит следующим образом:
Ваш долг теперь $100.00
Ваш долг теперь $110.00
Ваш долг теперь $121.00
Ваш долг теперь $133.10
Ваш долг теперь $146.41
Как вы уже смогли догадаться, для умножения debt на 1.1 также существует сокращенная запись.
Мы могли бы использовать выражение debt * = 1.1
для получения того же результата. Знак *= определяет "мультипликативную операцию присваивания", при выполнении которой значение переменной, расположенной слева, умножается на величину, стаящую справа. (См. пояснения ниже, на с. 226.)
6. В качестве третьего выражения можно использовать любое правильно составленное выражение. Какое бы выражение вы ни указали, его значение будет меняться при каждой итерации.
for(x = 1; у <= 75; у = 5*х++ + 10);
printf("%10d %10d\n", x, у);
В этом фрагменте выводятся на печать значения переменной x и алгебраического выражения 5*х +
10. Результат будет выглядеть так:
1 55 2 60 3 65 4 70 5 75
Обратите внимание, что в спецификации цикла проверяется значение у, а не x. В каждом из трех выражений, управляющих работой цикла for, могут использоваться любые переменные.
Хотя этот пример и правилен, он не может служить иллюстрацией хорошего стиля программирования. Программа выглядела бы гораздо понятнее, если бы мы не смешали процесс изменения переменной цикла с алгебраическими вычислениями.
7. Можно даже опустить одно или более выражений (но при этом нельзя опустить символы "точка с запятой"). Необходимо только включить в тело цикла несколько операторов, которые в конце концов приведут к завершению его работы.
ans = 2;
for (n = 3; ans <= 25;) ans = ans*n;
155

При выполнении этого цикла величина n останется равной 3. Значение переменной ans вначале будет равно 2, потом увеличится до 6, 18, а затем будет получена окончательная величина 54. (18
меньше 25, поэтому в цикле for выполняется еще одна итерация, и 18 умножается на 3, давая результат 54). Тело цикла for(; ;)
printf(" Я хочу сделать что-нибудь\n");
будет выполняться бесконечное число раз, поскольку пустое условие всегда считается истинным.
8. Первое выражение не обязательно должно инициализировать переменную. Вместо этого,
например, там мог бы стоять оператор printf( ) некоторого специального вида. Необходимо помнить только, что первое выражение вычисляется только один раз перед тем, как остальные части цикла начнут выполняться.
for(printf("Запоминайте введенные числа!\n"); num == 6;)
scanf(" %d", &num);
printf("Это как раз то, что я хочу!\n");
В этом фрагменте первое сообщение оказывается выведенным на печать один раз, а затем осуществляется прием вводимых чисел до тех пор, пока не помтупит число 6.
9. Параметры, входящие в выражения, находящиеся в спецификации цикла, можно изменить при выполнении операций в теле цикла. Предположим, например, что у вас есть цикл со спецификацией следующего вида:
for(n = 1; n < 1000; n + = delta)
И если после нескольких итераций ваша программа решает, что величина параметра delta слишком мала или велика, оператор if внутри цикла может изменить значение параметра. В диалоговой программе пользователь может изменить этот параметр в процессе выполнения цикла.
Короче говоря, большая свобода выбора вида выражений, управляющих работой цикла for,
позволяет с помощью этой конструкции делать гораздо больше, чем просто выполнять фиксированное число итераций. Возможности цикла for могут быть еще более расширены путем использования операций, которые мы вкратце обсудим ниже.
Резюме: оператор for
I. КЛЮЧЕВОЕ СЛОВО: FOR
II. ОБЩИЕ ЗАМЕЧАНИЯ:
В операторе for используются три выражения, управляющие работой цикла; они разделены символами "точка с запятой". Инициализирующее выражение вычисляется только один раз до начала выполнения какого-нибудь из операторов цикла. Если проверяемое выражение оказывается истинным (или не равным нулю), тело цикла выполняется один раз. Затем вычисляется величина корректируемого выражения, и значение проверяемого выражения определяется вновь. Оператор for - это цикл с предусловием: решение, выполнить в очередной раз тело цикла или нет, принимается до начала его прохождения. Поэтому может случиться так, что тело цикла не будет выполнено ни разу. Оператор, образующий тело цикла, может быть как простым, так и составным.
III. ФОРМА ЗАПИСИ:
for(инициализация; проверка условия; коррекция) оператор
156

Тело цикла выполняется до тех пор, пока проверяемое условие не станет ложным или равным нулю
III.ПРИМЕР
for(n = 0; n < 10; n++)
printf(" %d %d\n", n, 2*n + 1);
Выше уже упоминалось о том, что в языке Си имеется несколько операций присваивания.
Важнейшей из них является, конечно, операция =, при использовании которой значение выражения справа от знака присваивается переменной слева от него. Остальные операции присваивания корректируют значения переменных В записи каждой из них имеются имя переменной, стоящее слева от знака операции, и выражение справа от него Переменной присваивается новое значение,
равное старому, скорректированному с помощью величины выражения, стоящего справа. Результат зависит от используемой операции. Например: scores+= 20 то же самое, что scores = scores + 20,
dimes -= 20 то же самое, что dimes = dimes - 2, bunnies *= 2 то же самое, что bunnies = bunnies *
2, time /= 2.73 то же самое, что time = time / 2.73, reduce %= 3 то же самое, что reduce = reduce % 3.
Правые части здесь являются обыкновенными числами, но мы могли бы использовать и более сложные выражения х*= 3*у + 12
то же самое, что и х = х*(3*у + 12)
Этим операциям присваивания назначен тот же низкий приоритет, что и обычной операции =, т.е.
меньший, чем операциям + или *. Это и отражено в последнем примере. Вам совершенно не обязательно использовать все эти формы. Однако они более компактны, и при трансляции обычно позволяют получить более эффективный машинный код, чем традиционная, более длинная запись.
Они бывают особенно полезны в том случае, когда вы хотите поместить некоторое выражение в спецификацию цикла for.
Операция "запятая"
Далее
Содержание
Операция "запятая" увеличивает гибкость использования цикла for, позволяя включать в его спецификацию несколько инициализирующих или корректирующих выражений. Например, ниже приводится программа, которая выводит на печать величины почтовых тарифов первого класса обслуживания. (Во время написания этой книги почтовые тарифы были такими: 20 центов за первую унцию и по 17 центов за каждую следующую.)
/* почтовые тарифы */
#define FIRST 20
#define NEXT 17
main( )
{
int ounces, cost;
printf(" унции стоимость \n");
for(ounces = 1, cost = FIRST; ounces <= 16; ounces++, cost+ = NEXT)
printf(" %3d %7d\n" , ounces, cost);
}
Первые четыре строки результата работы программы будут выглядеть следующим образом:
унции стоимость
1 20 2
37 3
54 157

Мы воспользовались операцией "запятая" в первом и третьих выражениях: в первом случае она позволяет инициализировать переменные ounces и cost; во втором - на каждой итерации увеличивать значение ounces на 1, а cost на 17 (величину константы NEXT). Все вычисления осуществляются в спецификации цикла for. Применение операции "запятая" не ограничено только циклами for но именно в них она используется особенно часто. Операция обладает одним дополнительным свойством: при ее использовании гарантируется, что выражения, к которым она применяется (т. е. выражения, разделенные запятой), будут вычисляться слева направо. Поэтому переменная ounces будет инициализирована до переменной cost. В данном примере это не имеет значения, но порядок инициализации мог бы оказаться существенным, если выражение,
соответвующее cost, содержало бы переменную ounces. Символ "запятая" также используется как разделитель. Поэтому запятые в операторах: char ch, date;
РИС. 8.4. Операция "запятая" и цикл for
ИЛИ
printf(" %d %d\n", chimps, chumps);
являются разделителями, а не знаками операции "запятая".
резюме: наши новые операции
I. ОПЕРАЦИЯ ПРИСВАИВАНИЯ
Каждая из этих операций корректирует значение переменной слева от знака с помощью величины справа от него, в соответствии с указанной операцией. Ниже мы используем обозначение п.ч. для правой части, а л.ч. для левой части.
+= прибавляет величину п.ч. к переменной л.ч.
-= вычитает величину п.ч. из переменной л.ч.
*= умножает неременную л.ч. на величину п.ч.
/= делит переменную л.ч. на величину п.ч.
%= дает остаток от деления переменной л.ч. на величину и.ч.
ПРИМЕР:
rabbits *= 1.6; то же самое, что и rabbits * 1.6;
II. ДОПОЛНИТЕЛЬНЫЕ ОПЕРАЦИИ:ОПЕРАЦИЯ "ЗАПЯТАЯ"
158

Операция "запятая" связывает два выражения в одно и гарантирует, что самое левое выражение будет вычисляться первым. Обычно она используется для включения дополнительной информации в спецификацию цикла for.
Пример:
for(step == 2, fargo = 0; fargo < 1000; step *= 2)
fargo + = step;
Философ Зенон
2)
и цикл for
Далее
Содержание
Посмотрим, как с помощью операции "запятая" можно разрешить старый парадокс. Греческий философ Зенон утверждал, что пущенная стрела никогда не достигнет цели. Сначала, говорил он,
стрела пролетит половину расстояния до цели. После этого ей останется пролететь половину всего расстояния, но сначала она должна будет пролететь половину того, что ей осталось пролететь, и т.
д. до бесконечности. Поскольку расстояние полета разбито на бесконечное число частей, для достижения цели стреле может потребоваться бесконечное время. Мы сомневаемся, однако, что
Зенон вызвался бы стать мишеныо для стрелы, полагаясь только на убедительность своего аргумента. Применим количественный подход и предположим, что за одну секунду полета стрела пролетает первую половину расстояния. Тогда за последующую 1/2 секунды она пролетит половину того, что осталось от половины, за 1/4 - половину того, что осталось после этого, и т д. Полное время полета представляется в виде суммы бесконечного ряда 1 + 1/2 + 1/4 + 1/8 + 1/16 -1- ... . Мы можем написать короткую программу для нахождения суммы первых нескольких членов.
/* Зенон */
#define LIMIT 15
main ( )
{
int count;
float sum, x;
for(sum = 0.0, x = 1.0, count = 1; count <= LIMIT; count++, x *= 2.0)
{ sum + = 1.0/x;
printf(" sum = %f когда count = %d.\n" , sum, count);
}
}
В результате выполнения программы получим значения сумм, соответствующих первым 15
членам ряда:
sum = 1.000000 когда count = 1. sum = 1.500000 когда count = 2.
sum = 1.750000 когда count = 3.
sum = 1.875000 когда count = 4.
sum = 1.937500 когда count = 5.
sum = 1.968750 когда count = 6.
sum = 1.984375 когда count = 7.
sum = 1.992188 когда count = 8.
sum = 1.996094 когда count = 9.
sum = 1.998047 когда count = 10.
sum = 1.999023 когда count = 11.
sum = 1.999512 когда count = 12.
sum = 1.999756 когда count = 13.
sum = 1.999878 когда count = 14.
sum = 1.999939 когда count = 15.
Можно видеть, что, хотя мы и добавляем новые члены, сумма, по-видимому, стремится к какому-то пределу. И действительно, математики показали, что при стремлении числа членов к бесконечности сумма ряда сходится к 2,0, что и демонстрируется нашей программой. Какая
159
радость! Если бы Зенон оказался прав, движение было бы невозможно. (Но если бы движение было невозможно, то не было бы Зенона
3)
).
Что можно сказать по поводу самой программы? В ней показано, что в одном выражении можно использовать более, чем одну операцию "запятая". В спецификации цикла мы инициализировали переменные sum, x и count. После задания условий выполнения цикла оставшаяся часть программы оказывается очень короткой.
ЦИКЛ С УСЛОВИЕМ НА ВЫХОДЕ: do while
Далее
Содержание
Оба цикла, while и for, являются циклами с предусловиями. Проверка истинности условия осуществляется перед началом каждой итерации цикла. В языке Си имеется также конструкция цикла с постусловием (условием на выходе), где истинность условия проверяется после выполнения каждой итерации цикла. Этот подход реализуется с помощью цикла do while, который иллюстрируется следующим примером.
do
{
ch = getchar( );
putchar(ch);
} while(ch != ' \n')
Это сильно отличается от записи, например, такого вида while((ch = getchar( )) != '\n') putchar(ch);
Различие начинается с того момента, когда прочитан символ "новая строка". Цикл while печатает все символы вплоть до появления первого символа "новая строка", а цикл do while - все символы
вплоть до символа "новая строка" включительно. Только после печати этого символа в цикле производится проверка, является ли последний прочитанный символ символом "новая строка" В
цикле while эти действия осуществляются перед проверкой истинности условия. В общем виде цикл do while записывается следующим образом:
do оператор while(
выражение
);
Такой оператор может быть как простым, так и составным.
РИС. 8.5. Структура цикла do while.
160

Тело цикла do while всегда выполняется по крайней мере один раз, поскольку проверка осуществляется только после его завершения. Тело цикла for или while, возможно, не будет выполнено ни разу, поскольку проверка осуществляется перед началом его выполнения.
Использовать цикл do while лучше всего в тех случаях когда должна быть выполнена по крайней мере одна итерация, к примеру, мы могли бы применить цикл do while в нашей программе угадывания числа. На псевдокоде алгоритм работы программы можно тогда записать следующим образом:
do
{
выдвиньте предположение получите ответ вида д, б, или м } while(ответ не совпадает с д)
Вы должны избегать использования цикла do while, структура которого аналогична представленной ниже в записи на псевдокоде.
спросите пользователя, хочет ли он продолжать do некоторый умный вздор while(oтвет будет да)
В данном случае, после того как пользователь ответит "нет", "некоторый умный вздор" будет выполнен, поскольку проверка осуществляется слишком поздно.
Резюме: оператор do while
I. Ключевые слова: do while
II. Общие замечания:
Оператор do while определяет действия, которые циклически выполняются до тех пор, пока проверяемое выражение не станет ложным, или равным нулю. Оператор do while - это цикл с
постусловием; решение, выполнять или нет в очередное раз тело цикла, принимается после его прохождения. Поэтому тело цикла будет выполнено по крайней мере один раз. Оператор,
образующий тело цикла, может быть как простым, так и составным.
III. Форма записи do оператор while(
выражение
);
Выполнение оператора повторяется до тех пор, пока выражение не станет ложным, или равным нулю.
IV. Пример do scanf(" %d" , amp;number);
while(number != 20);
КАКОЙ ЦИКЛ ЛУЧШЕ?
Далее
Содержание
После того как вы решили, что вам необходимо использовать оператор цикла, возникает вопрос:
циклом какого вида лучше всего воспользоваться? Во-первых, решите, нужен ли вам цикл с
161
предусловием или же с постусловием. Чаще вам нужен будет цикл с предусловием. По оценкам
Кернигана и Ритчи; в среднем циклы с постусловием (do while) составляют только 5% общего числа используемых циклов. Существует несколько причин, по которым программисты предпочитают пользоваться циклами с. предусловием; в их числе один общий принцип, согласно которому лучше посмотреть, куда вы прыгаете, до прыжка, а не после. Вторым моментом является то, что программу легче читать, если проверяемое условие находится в начале цикла. И наконец,
во многих случаях важно, чтобы тело цикла игнорировалось полностью, если условие вначале не выполняется.
Положим, вам необходимо использовать цикл с предусловием. Что лучше: цикл for или цикл
while? Отчасти это дело вкуса, поскольку все, что вы можете сделать с помощью одного, вы можете сделать и с помощью другого. Для превращения цикла for в цикл while нужно опустить первое и третье выражения:
for(; проверка условия;)
Такая спецификация эквивалентна записи while(проверка условия)
Для превращения цикла while в цикл for необходимо предварительно осуществить инициализацию некоторых выбранных переменных и включить в тело цикла операторы, корректирующие их значения:
инициализация;
while (проверка условия)
{тело;
коррекция;}
Данная запись по своим функциональным возможностям эквивалентна следующей:
for(инициализация; проверка условия, коррекция) тело;
Исходя из соображений стиля программирования, применение цикла tor представляется более предпочтительным в случае, когда в цикле используется инициализация и коррекция переменной, а применение цикла while - в случае, когда этого нет. Поэтому использование цикла while вполне оправданно в случае while((ch = getchar( )) != EOF)
Применение цикла for представляется более естественным в случаях, когда в циклах осуществляется счет прохождений с обновлением индекса:
for (count = 1; count <= 100; count++)
ВЛОЖЕННЫЕ ЦИКЛЫ
Далее
Содержание
Вложенным называется цикл, находящийся внутри другого цикла. В этом разделе рассматривается пример, в котором вложенные циклы используются для нахождения всех простых чисел, не превышающих данного значения. Простое число - это такое число, которое делится нацело только на 1 и само на себя. Первыми простыми числами будут 2, 3, 5, 7 и 11.
Самый легкий способ узнать, является ли число простым, состоит в делении его на все числа между 1 и им самим. Если оно делится нацело на какое-нибудь число из этого ряда, то оно - не простое. Мы воспользуемся операцией деления по модулю (%) для проверки, выполнялось ли деление нацело. (Вы не забыли еще, конечно, операцию деления по модулю? Ее результатом
162
является остаток от деления первого операнда на второй. Если одно число делится на другое нацело, результатом операции деления помодулю будет 0.) При обнаружении какого-нибудь одного делителя числа дальнейшие проверки потеряют смысл. Поэтому в программе процесс проверки данного числа завершается после того, как найден его делитель.
Начнем с программы, проверяющей делимость одного числа. В ней имеется всего один оператор цикла.
/* простое число1 */
main( )
{
int number, divisor;
printf(" О каком числе вы хотите знать, простое ли оно?\n");
scanf(" %d" , &number); /* получение ответа */
while(number <2)
/* число отвергается */
{
printf(" Извините, мы не принимаем чисел меньше 2.\n");
printf(" Пожалуйста, попробуйте еще раз.\n");
scanf(" %d" , &number);
}
for(divisor = 2; number % divisor != 0; divisor++)
; /* проверка, простое число или нет,
осуществляется внутри спецификации цикла */
if (divisor == number) /* выполняется после завершения цикла */
printf(" %d - простое число.\n", number);
else printf(" %d - не простое число.\n", number);
}
Мы воспользовались структурой цикла while, чтобы избежать ввода значений, которые могли бы привести к аварийному завершению программы.
Обратите внимание, что все вычисления выполняются внутри спецификации цикла for. Величина переменной number последовательно делится на возрастающие значения делителей до тех пор,
пока не произойдет деление нацело (т. е. number % divisor станет равным 0). Если первым делителем, который приведет к такому результату окажется само это число, то значение переменной number - простое число. В противном случае данное число будет иметь меньший делитель, и это приведет к тому, что цикл завершится раньше.
Для нахождения всех простых чисел, меньших некоторой заданной величины, нам нужно будет заключить наш цикл for в некоторый другой цикл. На псевдокоде это будет выглядеть следующим образом:
для числа (number)=1 до верхнего предела limit проверять, является ли число простым
Вторая строка представляет собой нашу предыдущую программу.
Переводя эту запись на язык Си, получим программу:
/* простые числа2 */
main( )
{
int number, divisor, limit;
int count = 0;
printf(" Укажите, пожалуйста, верхний предел для поиска простых чисел.\n");
printf(" Верхний предел должен быть 2 или больше.\n");
scanf(" %d", &limit);
while(limit < 2) /* вторая попытка, если ошибка при вводе */
{
printf(" Вы были невнимательны! Попробуйте еще раз. \n");
scanf(" %d", &limit);}
printf(" Сейчас будут печататься простые числа!\n");
for(number = 2; number <= limit; number++) /* внешний цикл */
{ for(divisor =2; number % divisor != 0; divisor++)
;
if(divisor == number)
163

{ printf(" %5d", number);
if(++count % 10 == 0)
printf(" \n"); /* новая строка начинается через каждые 10 простых чисел */
}
}
printf(" \n Вот и все!\n");
}
Во внешнем цикле каждое число, начиная с 2 и кончая величиной limit, последовательно берется для проверки. Указанная проверка осуществляется во внутреннем цикле. Мы использовали переменную count для хранения счетчика получаемых простых чисел. При печати каждое одиннадцатое простое число мы начинаем с новой строки. Ниже приводится пример результатов,
получаемых с помощью такой программы:
Укажите, пожалуйста, верхний предел для поиска простых чисел.
Верхний предел должен быть 2 или больше.
250
Сейчас будут печататься простые числа!
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241
Вот и все!
Этот способ решения довольно прост, но он не является самым эффективным. Например, если вы хотите узнать, является ли число 121 простым или нет, нет необходимости проверять, существуют ли у него делители, превышающие 11. Дело в том, что если у данного числа существует делитель,
больший 11, то результатом деления будет число, меньшее 11; тогда этот делитель был бы обнаружен раньше. Поэтому требуется проверять только делители, не превышающие величину квадратного корня из числа, но в данном случае программа будет несколько сложнее. Мы оставляем ее в качестве упражнения любознательному читателю. (Указание: вместо того чтобы сравнивать делитель с величиной квадратного корня из числа, сравнивайте квадрат делителя с самим числом.)
ДРУГИЕ УПРАВЛЯЮЩИЕ ОПЕРАТОРЫ:
break, continue, goto
Далее
Содержание
Операторы, определяющие циклические вычисления, которые только что обсуждались, и условные операторы (if, if-else, switch) являются важнейшими средствами управления выполнением программы на языке Си. Они должны использоваться для реализации общей структуры программы. Три оператора, рассматриваемые ниже, обычно применяются реже,
поскольку слишком частое их использование ухудшает читаемость программы, увеличивает вероятность ошибок и затрудняет ее модификацию.
break:
Важнейшим из этих трех управляющих операторов является оператор break, который уже встречался нам при изучении оператора switch. Он может использоваться в операторе switch, где часто это просто необходимо, а также в циклах любого из трех типов. Когда в ходе выполнения программы встречается указанный оператор, его выполнение приводит к выходу из конструкций
switch, for, while или do while, в которых он содержится, и переходу к следующему оператору программы. Если оператор break находится внутри некоторой совокупности вложенных структур,
его действие распространяется только на самую внутреннюю структуру, в которой он непосредственно содержится.
164

Бывает, что break используется для выхода из цикла в тех случаях, когда заданы два разных условия прекращения его работы. Ниже приводится цикл, реализующий эхо-печать символов и завершающийся при чтении либо признака EOF, либо символа "новая строка":
while((ch = getchar( ))!= EOF)
{
if(ch == '\n') break;
putchar(ch);
}
Мы сделаем логику этою фрагмента программы более понятной, если объединим обе проверки в одном выражении:
while((ch = getchar( )) != EOF && ch != '\n') putchar(ch);
Если вы обнаружите, что break является частью оператора if, посмотрите, нельзя ли по-другому выразить это условие (как мы только что сделали), чтобы необходимость его использования отпала.
continue:
Этот оператор может использоваться во всех трех типах циклов, но не в операторе switch. Как и в случае оператора break, он приводит к изменению характера выполнения программы. Однако вместо завершения работы цикла наличие оператора continue вызывает пропуск "оставшейся"
части итерации и переход к началу следующей. Заменим оператор break в последнем фрагменте на continue:
while((ch = getchar( ))!==EOF)
{ if(ch == '\n')
continue;
putchar(ch); }
В версии, использующей оператор break, работа цикла полностью прекращается, как только при вводе встречается символ "новая строка". В версии с оператором continue просто пропускаются символы "новая строка", а выход из цикла происходит, только когда читается признак EOF.
Этот фрагмент, конечно, более компактно можно записать следующим образом:
while((ch=getchar( ))!= EOF)
if(ch != '\n') putchar(ch);
Очень часто, так же как и в данном случае, изменение условия в операторе if на обратное позволяет исключить необходимость введения в цикл оператора continue.
С другой стороны, оператор continue помогает иногда сократить некоторые программы,
особенно если они включают в себя вложенные операторы if else.
goto:
Оператор goto - одно из важнейших средств Бейсика и Фортрана - также реализован и в Си.
Однако на этом языке в отличие от двух других можно программировать, совершенно не используя указанное средство. Керниган и Ритчи считают оператор goto "чрезвычайно плохим" средством и предлагают "применять его как можно реже или не применять совсем".
Сначала мы покажем, как его использовать, а затем объясним, почему этого нe нужно делать.
Оператор goto состоит из двух частей - ключевого слова goto и имени метки. Имена меток образуются по тем же правилам, что и имена переменных. Приведем пример записи оператора
165
goto pait2;
Чтобы этот оператор выполнился правильно, необходимо наличие другого оператора, имеющего метку
1   ...   16   17   18   19   20   21   22   23   ...   42


написать администратору сайта