Продолжение, начало см. в МК 1-3, 5, 7, 9, 11, 14 (224-226, 228, 230, 232, 234, 237). Привет всем идущим в страну Си. А если вы еще не идете — присоединяйтесь. За два предыдущих перехода мы прошли оба сишных ветвления (if-else и switch) и цикл for. Теперь движемся дальше.

12. Меж собою так похожи Соколовы-братья...

Следующие два цикла, те, которые не то два, не то один (см. последний абзац предыдущего выпуска), — это циклы while («пока», не в смысле «до свиданья», а в смысле «до тех пор, пока»). Эти циклы выполняются, пока верно заданное условие, и отличаются друг от друга только тем, что в одном из них условие проверяется в начале (до тела цикла), а в другом — в конце (соответственно, после). За это их в некоторых книжках кличут «циклом с предусловием» и «циклом с постусловием». (Один мой знакомый, учивший язык по такой книжке, потом упорно называл первый из этих двоих «циклом с предисловием».)

Конструкция у них такая:

1. while(условие) оператор 2. do оператор while(условие)

Поэтому в других, менее косноязычных книжках их называют «цикл while» и «цикл do-while».

Перечисленные три варианта цикла (for, while и do-while) можно, конечно, все позаменять любым одним из них (с добавлением других операторов), как и делалось, к примеру, в ранних бейсиках (не Visual и не Turbo и даже не gw, а просто бейсиках) — там был только цикл for, причем очень урезанный. Но когда есть все три, то многое пишется значительно проще (не столько в смысле сложности написания, сколько по конструкции итогового исходника) — они вкупе дают уже достаточную свободу, пожалуй, для любых повторений, которые вы только сможете выдумать. Посему эти трое уже стали классикой — их, вслед за сями, включили в себя многие другие популярные языки. А Си во всем множестве широко используемых ныне языков, без сомнения, первенец; в той же незабвенной книге Кернигана-Ричи (тогда этот язык был еще очень молод) среди языков, с которыми его сравнивают, если и попадаются знакомые названия, то они знакомы скорее по исторической литературе.

Теперь приведу по одному примерчику для каждого из двух наших новых знакомых (конечно, с использованием и ранее пройденного — ветвлений и цикла for, для большей функциональности).

Пример первый:

#include void main() /* Программа просит у пользователя число (у нас уже традиция такая — просить у пользователей числа) и начинает его делить. Поделив на самые мелкие части — то есть на простые множители — возвращает (выводит) его пользователю в виде произведения этих частей */ { unsigned long n, /* сюда мы засунем число, которое даст нам пользователь */ div; /* а сюда мы будем складывать то, на что будем его делить */ printf( "Дайте-ка нам какое-нибудь число, какое не жалко, " "а мы его поделим на самые махонькие кусочки,\n" "такие, знаете, совсем простые, которые уже дальше " "делить не на что: "); scanf("%lu",&n); /* вводим число */ printf("%lu=",&n); /* и сразу выводим "число=", чтобы дальше после = дописать множители */ while(n>1) /* пока n>1, то есть пока оно будет хотя бы на что-то делиться, */ for( div=2;div<=n; /* начинаем наращивать потенциальный делитель */ div+=1+(div>2)) /* с вот таким интересным шагом, который в результате вычисления сравнения будет равен 1 при div==2 (т.к. div>2 в этом случае — 0) и 2 при div>2 (если в вашем компиляторе правда равна -1, поменяйте соответственно знак между единицей и скобками); таким образом мы пройдемся, начиная с 3, только по нечетным числам — оптимизация, конечно, слабенькая, но все же лучше проверять каждый раз одно дополнительное условие, зато прогонять в два раза меньше раз весь цикл */ while(!(n%div)) /* пока n делится на div... */ {n/=div; /* ...делим... */ printf("%lu*",div); /* ...и печатаем div со знаком * после него — для следующего множителя */ } printf("\b \n\n"); /* так как у нас после последнего множителя осталась лишняя звездочка, то нам ее надо удалить; для этого выводим символ «забой»; но этот символ сам по себе ничего не удаляет, а только возвращает курсор на одну позицию — поэтому после него выводим пробел, который загородит звездочку; ну и два Ентера — дабы отделить пустой строкой вывод следующей программы */ }

Пример второй:

#include void main() /* Хотите поиграться? Игрушка, правда, пока будет простенькая — будем угадывать числа. Точнее даже, вы будете загадывать, а машина (программа) угадывать (сама программа загадывать пока не умеет — ведь для этого нам надо выучить функции, связанные с генератором случайных чисел). Алгоритм такой: вы загадываете число (эта часть алгоритма, конечно, в программе не реализована); машина пытается угадать (угадывание ведется методом деления отрезка пополам) — вы ей говорите «больше», «меньше» или «попал». И так пока она не попадет. Тогда она радуется и признается вам, за сколько ходов она угадала. Только чур — не врать, а то поймает — обидится, не будет больше с вами общаться. */ { unsigned sml,mid,big; /* начало, середина и конец текущего отрезка; его середина будет текущей попыткой */ char ans,cnt; /* ответ пользователя и счетчик попыток */ do /* начало игры */ { sml=cnt=0; /* обнуляем начало отрезка и счетчик ходов; двойное присвоение — это не новая хитрая конструкция: просто присвоения выполняются справа налево и возвращают результат; поэтому их можно подставлять в выражения (здесь — в другое присвоение), то есть эту запись надо понимать как sml=(cnt=0) */ printf( "Загадайте число, а я буду отгадывать; загадывать " "можно от 0 до … (введите максимум): "); scanf("%u",&big); printf( "Теперь я буду отгадывать, а вы говорите, больше " "ваше число или меньше: если больше, ставьте + " "или >, если меньше, ставьте — или <. А если я " "угадаю — скажите Y (yes). Надоест — нажмите " "что-нибудь другое.\n"); mid=++big/2; /* для того чтобы угадывать до big включительно, увеличиваем его на 1, и уже это увеличенное значение (так как декремент спереди) делим на 2 и кладем в mid */ do /* начинаем угадывать */ { printf("%u ",mid); scanf("\n%c",&ans); /* выводим число и ждем, что нам на это скажут; в начале формата функции scanf зачем-то стоит \n — как вы понимаете, это неспроста, ведь эта функция принимает символ новой строки за окончание поля ввода, но не считывает его; поэтому после того как отработал предыдущий вызов вышеупомянутой функции, этот символ (\n) остается в потоке ввода, и для того чтобы прочитать в переменную не его, а то, что введут следующего, нужно его прочитать явным образом; такая хитрость нужна только при вводе символа, так как для других форматов символ \n воспринимается как пустая строка, а пустые строки функция scanf игнорирует */ switch(ans) /* начало хода */ { case '-': /* если сказали. что надо меньше... */ case '<': if(big==mid) /* если меньше уже некуда... */ { printf("Вы зачем врете? Я так не играю!\n\n"); /* ...ругаемся... */ return; /* ...и уходим */ } big=mid; /* уменьшаем большее... */ mid=(mid+sml)/2; /* ...и среднее */ break; case '+': /* если сказали, что надо больше — */ case '>': if(sml==mid) /* аналогично */ { printf("Вы зачем врете? Я так не играю!\n\n"); return; } sml=mid; mid=(mid+big)/2; break; case 'y': /* если угадано... */ case 'Y': break; /* ...продолжаем */ default: printf("Э-э... вы уже уходите? (y/n) "); /* если че-то левое — начинаем прощаться */ scanf("\n%c",&ans); if((ans|0x20)=='y') return; /* двоичные коды большого и маленького игреков равны соответственно 01011001 и 01111001; поэтому, если дополнительно установить третий слева бит (020 равно двоичному 00100000), то результат в обоих случаях (и только в этих двух случаях) будет равен 'y'; так что это своего рода оптимизация — вместо ans=='Y'&&ans=='y' */ cnt--; /* так как счетчик у нас сейчас увеличится, а при таком ответе нам его увеличивать не надо, то дабы нейтрализовать, заблаговременно уменьшаем */ } cnt++; /* так как все, что у нас только что произошло — это одна попытка, то, соответственно, увеличиваем счетчик попыток на единичку */ } while((ans|0x20)!='y'); /* если в ответе лежит какой-нибудь игрек — это означает, что свершился соответствующий бряк, то есть нам сказали, что мы угадали */ printf( "Ура! Я угадал число с %hu-й попытки. " "Давайте еще поиграем? (y/n) ",cnt); scanf("\n%c",&ans); } while((ans|0x20)=='y'); }

Ну вот, у нас уже есть все циклы. Теперь вернемся к оператору «бряк», о котором шла речь в прошлый раз. Помните (если, конечно, читали предыдущий выпуск), я писал, что этот оператор позволяет вываливаться не только из переключателя, но и из циклов? Поступает он с ними точно таким же образом — передает управление следующему после тела цикла оператору. А еще, если помните, я писал об операторе перехода, что единственный случай, где его, может быть, есть резон использовать, — это когда надо вывалиться из вложенных циклов. Дело в том, что если вы, к примеру, напишете вот так:

`for(i=1;i