Шестая глава в книжке Стивена Прата “C Primer Plus”.
Цикл с предусловием:
for (инициализация; проверка; обновление)
оператор
Инициализирующее выражение → проверяемое выражение → корректирующее выражение
Цикл с постусловием:
do
оператор
while (выражение)
Оперативная часть повторяется, пока выражение не станет ложным (false), т.е. равным нулю.
Содержание:
Греческий философ Зенон и цикл for
Греческий философ Зенон утверждал, что стрела никогда не поразит своей цели. Сначала, говорил он, стрела пролетает половину пути до цели. Затем она пролетает половину оставшегося пути, затем — половину того пути, который останется, и так до бесконечности.
Поскольку весь стрелы разбит на бесконечное количество частей, утверждал Зенон, стреле потребуется бесконечно большой промежуток времени для достижения конца пути. Однако мы сомневаемся в том, что Зенон добровольно согласился бы стать живой мишенью , чтобы доказать свою правоту.
На примере этой программы я впервые понял, насколько важно давать хорошие, годные имена переменным. Иначе программа превращается в настоящую кашу — со всеми этими n, x, y и проч. Мой вариант воплощения апории Зенона отличается от книжного, в нем рассматривается ситуация полета стрелы на сервере за некий бесконечно делимый тик на сервере:
#include <stdio.h>
int main (void)
{
int tick, limit;
double arrow, distance;
printf("Enter number of server ticks\n");
scanf("%d", &limit);
for (tick=1, arrow=1; tick <= limit; tick++, arrow=arrow/2)
{
distance = 1 - arrow;
printf("For %d tick arror would move %.6f distance\n",tick,distance);
}
getchar();getchar();
return 0;
}
Тогда как в книге представлено скорее упражнение с дробями.
…предположим, что стреле требуется одна секунда, чтобы пролететь первую половину пути. Затем ей понадобится 1/2 секунды, чтобы пролететь половину оставшегося пути, еще 1/4 секунды, чтобы преодолеть половину пути, который остался после этого, и т.д. Полное время пролета стрелы можно представить в виде следующей бесконечной последовательности:
1 + 1/2 + 1/4 + 1/8 + 1/16 + …
Таким образом в программе предлагается использовать ‘инкремент’ умножения знаменателя (англ. denominator) дроби на 2. Вот так (секунды я заменил тиками, это как-то более правильно):
#include <stdio.h>
int main (void)
{
int tick, limit;
double denominator, distance;
printf("Enter number of server ticks\n");
scanf("%d", &limit);
for (distance=0, tick=1, denominator=1; tick <= limit; tick++, denominator*=2)
{
distance+=1/denominator;
printf("For %d tick arror would make %.6f distance\n",tick,distance);
}
getchar();getchar();
return 0;
}
Вообще использование тут дробей доставило мне проблем при составлении программы (как я писал ранее, я стараюсь не подсматривать в книжку). В моем первой варианте все как-то гораздо понятнее. Ну с точки зрения учебника — доставление проблем это хорошо, мы же учимся 🙂 Вообще, цель этой программы было показать, что можно использовать более одной операции запятая.
Программа «угадай число» — цикл с постусловием (do while)
#include <stdio.h>
int main (void)
{
int n;
do
{
printf("Guess my favorite number!\n");
scanf("%d", &n);
} while (n != 13);
printf("Yay! It's 13!\n");
getchar();getchar();
return 0;
}
Пример цикла. использующего возвращаемое значение функции
Довольно долго корпел над этой программой из-за косяков с типами данных, потом не сходились результаты из-за < вместо <= 🙂
// power.c - program to power up number #include <stdio.h> double power (double a, int b); int main (void) { double a; // number which we want to power int b; // power value double result; // result of calculation printf("Enter number and power or 'q' to quit:\n"); while (scanf("%lf%d", &a, &b) == 2) { result = power (a, b); printf("%.3f powered by %d is %.3f\n", a, b, result); printf("Enter another number and power or 'q' to quit):\n"); } getchar();getchar(); return 0; } double power (double a, int b) { int count; // counter to get to power value double pow = 1.0; // power calculation (sum of multiplications) for (count = 1; count <= b; count++) pow *= a; return pow; }
Начинаются программы с более сложной структурой, поэтому буду их публиковать с отступами (придется из-за этого переносить комментарии, но что делать…).
Программа из контрольных вопросов (#5), где надо было найти ошибки; вот уже исправленный вариант:
#include <stdio.h> int main(void) { int i, j, list[10]; for (i = 0; i <= 9; i++) { list[i] = 2*i + 3; for (j = 0; j <= i; j++) printf(" %d", list[j]); printf("\n"); } getchar(); getchar(); return 0; }
Вывод:
3 3 5 3 5 7 3 5 7 9 3 5 7 9 11 3 5 7 9 11 13 3 5 7 9 11 13 15 3 5 7 9 11 13 15 17 3 5 7 9 11 13 15 17 19 3 5 7 9 11 13 15 17 19 21
В этой программе столкнулся тут с тем, что нужно «реверсивно» понять, что программа должна делать. Разобраться помог «пошаговый» режим компирятора (в Borland это называется Step Over, хоткей F8), в нем все встало на свои места 🙂
Шестая программа из контрольных вопросов — с вложенными циклами. Пока читаешь про вложенные циклы, кажется, что это довольно сложно сложно. На деле же, не так страшна малютка;по крайне мере, если задача относительно простая, как здесь, вывести золотой квадрат:
$$$$$$$$ $$$$$$$$ $$$$$$$$ $$$$$$$$
Выводим так:
#include <stdio.h> int main(void) { int col, row; for (row = 1; row <= 4; row++) { for (col = 1; col <= 8; col++) printf("$"); printf("\n"); } getchar(); return 0; }
При выполнении контрольного вопроса #5, я понял, что запутался в цикле do while, т.к. мне казалось, что в примере, который там указан, нужно напечатать лишнюю «Bye!»:
#include <stdio.h> int main(void) { int i = 0; while (++i < 4) printf("Hi! "); do printf("Bye! "); while (i++ < 8); return 0; }
Вывод:
Hi! Hi! Hi! Bye! Bye! Bye! Bye! Bye!
Чтобы разобраться, я изменил цикл и в «пошаговом» режиме компилятора увидел, как оно работает, рекомендую:
#include <stdio.h> int main(void) { int i = 1; do printf("%d ", i); while (i++ < 4); getchar(); return 0; }
Вообще, do while опасная штука.. Для аутентификации она отлично подходит, конечно, но запутаться довольно просто. Возможно, дело в привычке, надо набить руку 🙂
Про восьмую задачку я написал отдельную статью — уж больно задачка была получительная (не шути со scanf!).
Далее, меня накрыл ступор касательно очередности инкрементов.. Забавно — я только запомню что-то одно про С — так то, что ранее запоминал — выветривается из головы напрочь. Вот кейс, решил поэкспериментировать:
#include <stdio.h> int main(void) { char letter; letter = 'a'; printf("%c is %d\n\n", letter, letter); printf("%c is %d... now a++: %c is %d\n", letter, letter, (letter)++, (letter)++); getchar(); getchar(); return 0; }
Вывод:
a is 97
c is 99... now a++: b is 97
Чивоо? Начал курить и вспомнил, что сначала инкременты эти все срабатывают, а потом уже все печатается (а не по одному, как мне казалось) 🙂 Ну ёлы-палы. Вот что значит «мало практиковаться» (МИФ). Но черт побери, почему выводится c is 99... now a++: b is 97)
? Видимо это какая-то рекурсия, которая берется хрен знает откуда.. Боюсь представить, что можно натворить такими кривыми выражениями 😀 Тьфу-тьфу-тьфу, чур меня!
Упражнения по программированию для этой главы я также вынесу в отдельный пост, т.к. тут уже порядочно теста, а в упражнениях аж целых 18 заданий и многие из них непростые.
Чего-то в начале куски кода не форматированные.
сначала я форматировал через <code>.. с ним легче постить, не надо париться за переносы. было вполне ок, т.к. не было циклов сложных. но теперь уже приходится через <pre>, чтобы отступы показывать
Решил полюбопытствовать то же. Позапускать примерчики. Оказывается и для Си есть онлайн компиляторы, очень удобно для целей изучения.
Вот например: https://www.onlinegdb.com/online_c_compiler
Еще зацепился глаз за повторяющиеся вызовы
getchar();getchar();
Понятно что это все поиграться, потыкаться. Но думаю будет интересно. «Проблема» в scanf, который оставляет, нажатый при вводе числа, Enter в подвисшем состоянии (ожидающим буфера) и первый getchar его глатает сразу. Погуглил — знающие люди рекомендуют сразу приучаться использовать заданный, ограниченный буфер. Это будет и безопаснее и не будет этих проблем. Соответственно использовать fgets и sscanf (или сразу strtol, если получаем число).
getchar();
— эт вполне каноничный способ «держать» консоль открытой. Тут не только в scanf дело, любая программа закрывается, когда ее запускаешь из борланда без getchar(). Это выходит гораздо быстрее, чем билдить, открывать виндовую консоль и там запускать экзешник.Ты не понял. Я говорю про то, почему тебе два раза приходится вызывать getchar(). Это не совсем правильно и это приходится делать именно из-за scanf. Я не говорю что не надо getchar() в конце вызывать, если ты хочешь сделать что-то типа «Press any key to exit…». Однозначно это удобно.
ну эт чисто заглушка во время обучения)