Skip to main content
2 votes

Доброго времени суток! Добро пожаловать на четвертый урок по основам программирования на С++. Давайте сначала вспомним что мы уже успели пройти: структура программы, ввод-вывод данных, переменные и их типы, математические операции. Давайте вспомним это и напишем такой код:

  1. #include "stdafx.h"
  2. #include <iostream>
  3.  
  4. using namespace std;
  5.  
  6. int _tmain(int argc, _TCHAR* argv[])
  7. {
  8. float a = 0, b = 0, c = 0;
  9. cout << "Please, insert two numbers" << endl;
  10. cin >> a >> b;
  11. c = a / b;
  12. cout << "a / b = " << c << endl;
  13. getchar();
  14. return 0;
  15. }

Он правильный и будет работать до тех пор, пока пользователь не задаст значение b = 0 . Ведь всем известно, что делить на 0 нельзя. А что если не всем? Для таких случаев необходимо защищать программу от ошибок пользователя.

error.PNG

До сих пор мы писали программы, реализующие линейные алгоритмы, то есть «идущие по прямой», грубо говоря. Сейчас же возникла ситуация, когда программа должна проверить правильность ввода переменных. Для этого мы используем так называемую конструкцию ветвления. Ниже представлены 2 схемы таких ветвящихся алгоритмов.

if if-else.PNG

Справа представлен случай, когда мы в зависимости от условия выполняем либо одно действие либо другое (зашли в подъезд, пойдем по лестнице или поедем на лифте). Слева же представлен случай, когда мы либо выполняем действие, либо игнорируем его. Код. Организовать такое ветвление позволяет конструкция if-else (для правой схемы) либо просто if (для левой):

  1. if (условие)
  2. {
  3. операторы 1;
  4. }
  5. else
  6. {
  7. операторы 2;
  8. }

Рассмотрим все построчно: какое может быть условие? Абсолютно любое. В нашем случае следует написать условие b = 0. Операторы 1 будут выполнены, если условие, написанное в скобках, оказалось верным. Операторы 2 будут выполнены, если условие не выполнены. else и операторы 2 можно опустить, тогда получится код, удовлетворяющий левой схеме.
Может быть кто-то заинтересовался, что за фигурные скобки. Эти скобки называются операторными. Формально, конструкция if-else позволяет выполнить лишь 1 действие, например:

  1. if (a == 1)
  2. cout << “a=1”;
  3. cout<<”thanks”;

Если а = 1, то на экран будет выведено 2 сообщения: а=1 и thanks. Если а не равно 1, то будет выведено лишь сообщение thanks. Это то, о чем я говорил. Если мы, например, оба вывода объединим с помощью операторных скобок, то при а=1 будут выведены обе строки, а при любых других значениях а – ни одна из строк не будет выведена.

  1. if (a == 1)
  2. {
  3. cout << “a=1”;
  4. cout<<”thanks”;
  5. }

Как вы уже заметили выше, знак двойного равенства в С++ эквивалентен знаку математического равенства в обычной жизни, а одинарное равенство в С++, как мы знаем, операция присваивания. Помимо равенства можно сравнивать значение на неравенство (!=), присутствуют знаки больше (>), меньше (<), больше или равно (>=) и меньше или равно (<=). Они все могут присутствовать при проверке условий.
Внутри операторных скобок можно выполнять любые законные действия. Давайте попробуем добавить в нашу программку, код которой приведен в начале урока, защиту от ошибок пользователя. Сначала на словах: спрашиваем у пользователя 2 числа, потом сравниваем второе (знаменатель) с нулем. Если совпало, выводим сообщение об ошибке, если нет – выполняем деление. Тогда главная функция нашей программы принимает следующий вид:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. float a = 0, b = 0, c = 0;
  4. cout << "Please, insert two numbers" << endl;
  5. cin >> a >> b;
  6. c = a / b;
  7. if (b == 0)
  8. {
  9. cout << "Error b-value. Division is impossible." << endl;
  10. }
  11. else
  12. {
  13. cout << "a / b = " << c << endl;
  14. }
  15. getchar();
  16. return 0;
  17. }

Теперь при вводе неправильного значения программа не выполняет ошибочные операции, а корректно завершает свою работу:

error with checking.PNG

Защита программы от ошибок пользователя – очень важный элемент, и не стоит уделять ему недостаточно внимания, потому что ошибки бывают разные, и самые тяжелые из них могут приводить к потере важных данных и обрушению системы в целом, что, конечно же, недопустимо.
Также вы, должно быть, обратили внимание, что в каждых «новых» операторных скобках я операторы пишу с небольшим отступом, по сравнению с операторными скобками. Это также относится к хорошему стилю программирования. Вообще говоря, есть разные способы оформлять свой код, называть переменные и так далее. Очень многое описано в так называемых нотациях программирования, в частности, венгерской нотации, которая, например, принята за стандарт оформления в Microsoft. Это же относится и к тому моменту, что я одиночный оператор в конструкции if-else обрамил операторными скобками, ведь можно было написать так:

  1. if (b == 0)
  2. cout << "Error b-value. Division is impossible." << endl;
  3. else
  4. cout << "a / b = " << c << endl;

Или так:

  1. if (b == 0) cout << "Error b-value. Division is impossible." << endl;
  2. else cout << "a / b = " << c << endl;

Компилятору нет разницы, как именно вы напишете подобные строки кода. Зато для вас, как программистов, или для других программистов, которые будут потом работать с вашим кодом, очень большая разница. Когда программа объемная, табуляция (отступы) бросаются в глаза, благодаря ей легко определить начало и конец отдельных групп операторов.
Мы отвлеклись, но давайте все же вернемся к нашей программе. Если пользователь ввел неправильное значение, то ему выводится сообщение об ошибке и программа закрывается. Не очень красиво, правда? Почему бы ему не предоставить еще один шанс на ввод? Можно. Но мы не будем еще раз писать ввод с клавиатуры. Мы воспользуемся такой конструкцией как циклы. Циклы позволяют выполнить одно действие (или набор одних и тех же действий) много раз. Циклы в С++ бывают 3 видов: цикл с предусловием, цикл с постусловием и цикл с параметром. Их схемы представлены ниже. Начнем с цикла с предусловием do-while:

do-while scheme.PNG

Сначала выполняется проверка определенного условия (в нашей задаче это b == 0), если проверка пройдена успешно, выполняются операторы, расположенные в теле цикла, после их выполнения опять проверяется условие и так происходит до тех пор, пока проверка условия не провалится, в этом случае выполняется не цикл, а следующий за ним код. Далее цикл с постусловием while:

while scheme.PNG

Как иногда шутят программисты, программа в данном цикле сначала делает, а потом думает чего же она такого интересного наделала. Отличие от цикла с предусловием только в очередности выполнения тела цикла и проверки условия.
И напоследок цикл с параметром for:

for scheme.PNG

Что такое параметр. Параметр это некоторая переменная, значение которой и является критерием завершения цикла, если так можно выразиться. У нас есть некоторая переменная, зачастую i, при первой проверке условия переменная проверяется как есть (она должна быть инициализирована заранее), потом после каждого прохода цикла (итерации) переменная изменяет свое значение так, как скажет программист (например, увеличивается на 1: i++), и так продолжается до тех пор, пока условие не перестанет выполняться.
Давайте посмотрим как будет выглядеть наша программа, если мы добавим в нее цикл (попробуем с каждым). Итак, цикл с предусловием.

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. float a = 0, b = 0, c = 0;
  4. while (b == 0)
  5. {
  6. cout << "Please, insert two numbers" << endl;
  7. cin >> a >> b;
  8. if (b == 0)
  9. {
  10. cout << "Error b-value. Division is impossible." << endl;
  11. }
  12. }
  13. c = a / b;
  14. cout << "a / b = " << c << endl;
  15. getchar();
  16. return 0;
  17. }

Некоторые из вас спросят зачем же if, мы ведь его рассмотрели? Верно, но без него либо сообщение об ошибке будет выводиться даже после первого захода (мы инициализировали b нулем, чтобы цикл выполнился хотя бы раз, еще до ввода значений пользователем). Как вы поняли, вкладывать ветвления и циклы друг в друга можно. Сколько раз? Сколько этого требует каждый отдельно взятый случай. Почему вычисления вне цикла? Потому что цикл гарантирует нам, что после его выполнения у нас будет правильный знаменатель и ошибка, которую мы ловим, тут невозможна.
Перейдем к циклу с постусловием.

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. float a = 0, b = 0, c = 0;
  4. do
  5. {
  6. cout << "Please, insert two numbers" << endl;
  7. cin >> a >> b;
  8. if (b == 0)
  9. {
  10. cout << "Error b-value. Division is impossible." << endl;
  11. }
  12. }
  13. while (b == 0);
  14. c = a / b;
  15. cout << "a / b = " << c << endl;
  16. getchar();
  17. return 0;
  18. }

Как мы можем видеть, все абсолютно аналогично, только while теперь находится в конце цикла, а начинается цикл со слова do. Все просто. Результаты как всегда ниже.

do-while.PNG

Цикл с параметром должен иметь параметр, считать его. Давайте, например, дадим пользователю 3 попытки ввода, чтобы продемонстрировать его работу.

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. float a = 0, b = 0, c = 0;
  4. for (int i = 3; i > 0; i--)
  5. {
  6. cout << "Please, insert two numbers. " << i << " attempts remaining." << endl;
  7. cin >> a >> b;
  8. if (b == 0)
  9. {
  10. cout << "Error b-value. Division is impossible." << endl;
  11. }
  12. }
  13. c = a / b;
  14. cout << "a / b = " << c << endl;
  15. getchar();
  16. return 0;
  17. }

Смотрите, начало цикла уже сложнее, чем в рассмотренных случаях. Разберем его подробнее. В общем случае он выглядит следующим образом:

  1. for (инициализация параметра; условие; вычисление)
  2. {
  3. тело цикла;
  4. }

Но это лишь в общем случае. В зависимости от текущей обстановки данный цикл может выглядеть иначе. Например, если у нас инициализация параметра произведена раньше, то в цикле ее не будет (for ( ; условие; вычисление)). Или если пересчет параметра происходит в теле цикла (for (инициализация; условие;)). Или все вместе (for ( ; условие;)). В принципе, можно даже бесконечный цикл сделать, то есть цикл, в котором условие выхода из цикла не выполнится никогда, да и инициализация не проводится с пересчетом параметра (for (; ;)). Давайте вернемся к нашей задаче и посмотрим, что же мы наделали.

simple for.PNG

Работает, круто, правда? Но он же не производит проверку. Да и выполняется в любом случае 3 раза, даже если пользователь ввел все правильно. Первая мысль: надо менять условия. И тут мы вводим понятия сложных условий. Сложное условие, это условие, состоящее из простых условий. Простые условия мы рассматривали до этого. Как происходит объединение условий? Посредством логического И (&&), ИЛИ (||). Логическое И истинно тогда и только тогда, когда оба операнда истинны. Логическое ИЛИ истинно если хотя бы один из операндов истинен. То есть можно записать что-нибудь типа:

  1. for (int i = 3; i > 0 || b != 0; i--)

Вторая мысль, которая приходит после обдумывания первой мысли, заключается в том, что в условии проверяется значение параметра, поэтому запихивать туда еще что-то – не лучшая идея. Тогда нужно внутри цикла посмотреть на правильность ввода и как-то выйти из цикла без проверки условий. Это можно. Завершить текущую итерацию цикла можно оператором continue. Завершить весь цикл можно оператором break. Эти операторы не относятся к структурным, так как могут завершить выполнение текущего фрагмента программы против логики самой программы. Наш преподаватель сильно ругался, когда мы так делали, но, как говорится, если нельзя, но очень хочется, то можно. Наша программа приняла такой вид:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. float a = 0, b = 0, c = 0;
  4. for (int i = 3; i > 0; i--)
  5. {
  6. cout << "Please, insert two numbers. " << i << " attempts remaining." << endl;
  7. cin >> a >> b;
  8. if (b == 0)
  9. {
  10. cout << "Error b-value. Division is impossible." << endl;
  11. }
  12. else
  13. {
  14. break;
  15. }
  16. }
  17. c = a / b;
  18. cout << "a / b = " << c << endl;
  19. getchar();
  20. return 0;
  21. }

И результаты ниже:

for-break.PNG

Все работает, все хорошо, поздравляю!
Мы закончили рассмотрение текущей темы на сегодня, добавлю лишь одно: результаты всевозможных сравнений переменных на равенство имеют 2 исхода – истина или ложно, то есть являются логическим типом. Отсюда можно сделать вывод, что цикл while (true) будет работать всегда. И это правда. Точно также как цикл while (false) никогда не запустится. 0 также воспринимается как false и абсолютно любое ненулевое значение как true, если вы не забыли. Например, в играх используются бесконечные циклы, когда нельзя прерывать игровой процесс никак иначе кроме как по результатам игры, либо по желанию игрока.
На этом давайте завершим наш сегодняшний урок. Домашнее задание будет заключаться в следующем: запросить у пользователя переменную, если она четная, необходимо увеличить ее в 3 раза и вывести в текстовый файл, если нечетная – вывести на экран 10 раз. Продемонстрировать оба варианта выполнения. Напоминаю также, что домашнее задание нужно сдать в течение недели мне на проверку (можно отправить скриншоты работающей программы и обязательно листинг). Удачной недели и до скорых встреч.

Аватар пользователя Шевченко Станислав

Спасибо