Зачем мне это?

Прежде, чем обращаться к кому-то за помощью в обучении, важно ответить себе на этот вопрос.

Множество полезной информации для самостоятельного обучения программированию уже и так свободно доступно в Интернете. Более того, этой информации вполне достаточно, чтобы самостоятельно научиться производить работоспособные решения задач.

Самообучение — это прекрасно. Встает крепкий вопрос: зачем кому-либо что-то еще?

Рассмотрим процесс решения несложной задачи, которая требует познаний по математике уровня 3-го класса.

Простая задача

Получить два целых числа — нижнюю и верхнюю границы диапазона. Вывести по одному в строке все целые числа, входящие в этот диапазон и кратные трем.

Пример работы программы

Ввод: 2 9

Вывод: 3 6 9

Решение 1. Код быстрого приготовления

Напишем простейшее решение, которое приходит в голову.

#include <stdio.h> int main() { int a, b; scanf("%d %d", &a, &b); for ( ; a <= b; a++ ) { if ( a % 3 == 0 ) { printf("%d\n", a); } } return 0;}

Просто, быстро написано, на числах 2 и 9 работает. Но есть один нюанс... Зачем перебирать все числа и вычислять остатки, если нужно вывести каждое третье? Что, если в диапазоне окажется миллиард чисел вместо восьми?

Решение 2. Математика, третий класс

Приведем начало диапазона к кратности и будем просто выводить каждое третье число. Тут и далее изменяемые части кода подсвечены синим фоном.

#include <stdio.h> int main() { int a, b; scanf("%d %d", &a, &b); if ( a % 3 == 1 ) { a = a + 2; } if ( a % 3 == 2 ) { a = a + 1; } for ( ; a <= b; a += 3 ) { printf("%d\n", a); } return 0;}

Хорошо, это будет работать быстрее. Теперь можно обратиться к поисковой системе с запросом «shorthand operators» и экономить в будущем силы на нажатие лишних кнопок при наборе кода.

Решение 3. Краткость — сестра таланта

Заменим конструкцию a = a + ... на a += ...

#include <stdio.h> int main() { int a, b; scanf("%d %d", &a, &b); if ( a % 3 == 1 ) { a += 2; } if ( a % 3 == 2 ) { a += 1; } for ( ; a <= b; a += 3 ) { printf("%d\n", a); } return 0;}

Неясно, что такое a и b. Когда программа будет развиваться, она рискует превратиться в набор совершенно никому не понятных идентификаторов.

Решение 4. «М» — это для морды

Если сопоставить имена переменных с условием задачи, то а — это начало диапазона, b — его конец. Более уместны и понятны для них будут названия min и max.

#include <stdio.h> int main() { int min, max; scanf("%d %d", &min, &max); if ( min % 3 == 1 ) { min += 2; } if ( min % 3 == 2 ) { min += 1; } for ( ; min <= max; min += 3 ) { printf("%d\n", min); } return 0;}

Зачем всегда дважды вычислять остаток и проверять min на кратность?

Решение 5. Два раза не повторяем, не повторяем

Во-первых, вычислим остаток один раз и запомним его в переменной с логичным названием remainder (англ. остаток). Во-вторых, добавим ключевое слово else между условиями, так как остаток не может оказаться двойкой, если он уже оказался единицей.

#include <stdio.h>int main() { int min, max; int remainder; scanf("%d %d", &min, &max); remainder = min % 3; if ( remainder == 1 ) { min += 2; } else if ( remainder == 2 ) { min += 1; } for ( ; min <= max; min += 3 ) { printf("%d\n", min); } return 0;}

Значение переменной min (нижней границы диапазона) непоколебимо стремится вверх, теряя соответствие своего названия содержимому. Под конец работы программы в ней уже будет явно не минимум.

Решение 6. На заборе написано «min». А там доски

Лучше всего характер этого меняющегося значения, кратного тройке, определяет слово «кратное». По-английски — multiple.

#include <stdio.h>int main() { int min, max; int remainder; int multiple; scanf("%d %d", &min, &max); multiple = min; remainder = min % 3; if ( remainder == 1 ) { multiple += 2; } else if ( remainder == 2 ) { multiple += 1; } for ( ; multiple <= max; multiple += 3 ) { printf("%d\n", multiple); } return 0;}

Если для поиска кратного тройке числа программисту потребовалось дважды произвести проверку, то сколько же их будет, когда нужно будет найти число, кратное сорока двум? Сорок одна? А кратное миллиарду?

Решение 7. Расширяем горизонты

Применим более продвинутые познания арифметики.

Неожиданно, если из числа вычесть остаток от его деления на три, то результат будет делиться на три! Важно только соблюдать условие задачи и убедиться, что результат не меньше минимума.

#include <stdio.h>int main() { int min, max; int multiple; scanf("%d %d", &min, &max); multiple = min - min % 3; if ( multiple < min ) { multiple += 3; } for ( ; multiple <= max; multiple += 3 ) { printf("%d\n", multiple); } return 0;}

В коде присутствуют «магические» числа. Что обозначает тройка? И что будет, если вместо кратности тройке нужно будет вывести числа, кратные пяти? Нужно вносить правки в несколько мест? Больше правок — выше вероятность ошибок.

Решение 8. Разоблачение магии

Введем именованную константу под названием divisor (англ. делитель).

#include <stdio.h>int main() { const int divisor = 3; int min, max; int multiple; scanf("%d %d", &min, &max); multiple = min - min % divisor; if ( multiple < min ) { multiple += divisor; } for ( ; multiple <= max; multiple += divisor ) { printf("%d\n", multiple); } return 0;}

На данной прекрасной ноте мы завершим работу над этой простой задачей, которая теперь позволяет эффективно находить кратные числа и легко менять делитель.

Выводы

В реальной жизни задачи будут объемнее и сложнее, а различные ошибки, невнимательность и недоработки в коде могут в итоге приводить к гораздо более серьезным и иногда даже очень печальным последствиям.

Cтатьи, документация, поисковые системы и прочие инструменты самообучения — это великолепно и мы призываем вас активно ими пользоваться. У них множество плюсов и зачастую только один общий минус — они не дадут обратную связь и комментарии к вашим решениям.

PS. Мы намеренно «упустили», что решения 2–6 некорректно работают с отрицательными числами, так как остаток может оказаться −1 или −2. Кто самостоятельно заметил — молодец.

PPS. Хотите улучшить свои шансы писать нормальный код? Наверное, вы уже догадываетесь, куда следует пойти.