Qbik-club
Дата публикации:03.11.21 12:09;Автор:Евгений;Категория: программирование;Теги:, , ;

Циклы и условные операторы

Друзья, в нашей череде публикций, посвящённой урокам по С++, мы уже достаточно далеко углубились в мир программирования. Уже заем о переменных, их типах, преобразовании, функциях, передаче... Но вот упустили из виду ещё один момент. Это условные операторы. К сожалению да, упустил из виду. Давайте же сегодня заполним этот пробел в знаниях и узнаем, что такое условные операторы, циклы, зачем они нужны и как применять.

Циклы и условные операторы

Условные операторы

И так, давайте по традиции начнём с примера кода:

#include <iostream>

using namespace std;

bool IssAdmin(){
    return true;
}

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    if(IssAdmin()){
        cout << "Разрешаем доступ" << endl;

}
    else{
        cout << "Запрещаем доступ" << endl;

}
    
    return 0;
}

И сразу же запустим, посмотрим, что получим в итоге.

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

И так, видим, что в функции main() есть условный оператор if, в который передана функция, возвращающая значение типа bool. В первом уроке мы уже говорили, что переменные с этим типом могут иметь два значения true и false. Но вот для чего они нужны, мы до сих пор не знали и не применяли на практике. Так вот, переменные с этим типом используются для условных операторов. Дословно их значения можно так и перевести. true — правда. false — ложь.

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

Мы пишем if, затем переменную или функцию, которая будет хранить или возвращать нечто, что можно интерпретировать  как булев тип (о преобразовании типов мы уже говорили в прошлой публикации). Далее код, который должен выполниться, если нам вернули true. Если же было возвращено false, т.е. ложь, то данный код пропускается до тех пор, пока не попадём в блок else и будет выполнен код в этом блоке.

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

#include <iostream>

using namespace std;

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int Priv = 1;
    if(Priv == 1){
        cout << "Полный доступ к настройкам" << endl;
    }

    else if(Priv == 2){
        cout << "Ограниченный доступ" << endl;
    }

    else{
        cout << "Запрещаем доступ" << endl;
    }
    
    return 0;
}

Код работает, но согласитесь, получается довольно громоздко. По этому человечество придумало второй тип условного оператора. Это — switch. Логика тут ровно такая же, но с тем изменением, что он изначально рассчитан на множество вариантов. Иногда его так и называют, оператором множественного выбора. Или оператором множества. Давайте посмотрим реализацию нашего примера в таком случае.

#include <iostream>

using namespace std;

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int Priv = 4;

    switch(Priv){
        case 1:
            cout << "Полный доступ к настройкам" << endl;
            break;

        case 2:
            cout << "Ограниченный доступ" << endl;
            break;

        default:
            cout << "Запрещаем доступ" << endl;
    }

    return 0;
}

Тут мы в начале пишем switch, затем в скобках значение, которое мы будем проверять. Обратите внимание, что в отличии от if, тут мы его указываем один раз. Затем в фигурных скобках пишем case и через пробел значение, которое должны получить для того, чтоб попасть в данный блок. Обратите внимание, что каждый блок обязательно должен заканчиваться ключевым словом break! После этого ключевого слова — мы пишем следующий кейс. Или как можно перевести на Русский — случай. После того, как мы написали все возможные варианты, в нашем случае их два, мы можем написать default. Это блок по умолчанию, который будет выполнен в том случае, если мы не попали ни в один из кейсов. Обратите внимание, что в примере выше я описал два кейса. на случай, если в переменной будет один или два. Но у нас переменная хранит четвёрку, что значит, что в данном случае мы как раз и попадём в default.

Лайфхаки по работе с условными операторами

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

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int Priv = 1;
    if(Priv == 1)
        cout << "Полный доступ к настройкам" << endl;

    else if(Priv == 2)
        cout << "Ограниченный доступ" << endl;

    else
        cout << "Запрещаем доступ" << endl;
    
    return 0;
}

Если вы запустите данный код, то увидите, что работает он точно так же, как и код выше. Но зачем тогда скобки? Тут есть одно очень важное правило! Фигурные скобки мы можем не писать только тогда, когда в теле только одно действие! В данном случае — вывод строки. Т.е. вот так — писать нельзя:

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int Priv = 1;
    if(Priv == 1)
        cout << "Полный доступ к настройкам" << endl;
        cout << "Да, честно, не вру!" << endl;
    else
        cout << "Запрещаем доступ" << endl;
    
    return 0;
}

Как видим, в блоке появилась вторая строка и тут уже эти два действия мы должны обернуть фигурными скобками в любом случае, иначе компилятор не сможет понять, какая строка к чему относится и не сможет скомпилировать программу. в данном случае, мы можем написать так:

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int Priv = 1;
    if(Priv == 1){
        cout << "Полный доступ к настройкам" << endl;
        cout << "Да, честно, не вру!" << endl;
    }
    
    else
        cout << "Запрещаем доступ" << endl;
    
    return 0;
}

Так всё скомпилируется и запустится. Но что делать, если блок else нам не нужен? К примеру запросы от не авторизированных пользователей мы хотим просто игнорировать? Мы его можем просто не писать. Т.е. сократить наш код до такого варианта:

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int Priv = 1;
    if(Priv == 1)
        cout << "Доступ разрешён." << endl;
    
    return 0;
}

Обратите внимание, что если переменную Priv мы заменим, к примеру, на двойку, то программа просто ничего не будет выводить на экран. Блок if будет пропускаться, а другого кода просто нет. В итоге будем видеть просто пустой экран.

Точно так же можем поступить и со switch. Дефолтный блок — можем пропустить.

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int Priv = 4;

    switch(Priv){
        case 1:
            cout << "Полный доступ к настройкам" << endl;
            break;

        case 2:
            cout << "Ограниченный доступ" << endl;
            break;
    }

    return 0;
}

Теперь тоже, если ни в один блок не попадаем — программа ничего выводить не будет.

Тернарный оператор

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

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

#include <iostream>

#include <string>

using namespace std;

bool IssAutch(){
    return true;
}

void PrintText(string &text){
    cout << "[Лог]: " << text << ";" << endl;
}

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    string LogText;
    if (IssAutch())
        LogText = "Вы авторизированы";

    else
        LogText = "Вы НЕ авторизированы";
    
    PrintText(LogText);

    return 0;
}

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

Задача на внимательность! Почему переменная LogText сразу была создана, до if, только затем записываем значение? Если не знаете почему — добро пожаловать в урок про область видимости переменных!

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

#include <iostream>

#include <string>

using namespace std;

bool IssAutch(){
    return true;
}

void PrintText(string &text){
    cout << "[Лог]: " << text << ";" << endl;
}

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    string LogText = (IssAutch()) ? "Вы авторизированы" : "Вы НЕ авторизированы" ;
    PrintText(LogText);
    return 0;
}

Как видим, мы тут создали переменную, затем написали оператор = и в скобках, точно таких же, как в случае с if, написали условие. После чего поставили знак вопроса. Это и есть тернарный оператор, который говорит компилятору, что тут, в зависимости от того, что мы получили в скобках, есть два значения. Среди них нужно выбрать. Если значение было true, то будет подставлено первое значение. Если же false — то значение до двоеточия будет пропущено и подставлено то, что стоит после двоеточия.

Таким образом мы буквально в одну строку получаем тот же результат, который бы с if занял 4 строки. Но тут есть один недостаток. Использовать тернарный оператор мы можем только в случае, когда нужно выбрать одно из двух значений. Т.е. в случае, когда нужно выполнить несколько действий или когда выбор более, чем из двух значений — данный вариант не подойдёт.

Ну а теперь совсем хардкорный вариант, который выбрали бы тру-программеры! :)

#include <iostream>

using namespace std;

bool IssAutch(){
    return true;
}

void PrintText(string text){
    cout << "[Лог]: " << text << ";" << endl;
}

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    PrintText((IssAutch()) ? "Вы авторизированы" : "Вы НЕ авторизированы");
    return 0;
}

Как видим, тут мы даже не создаём переменную. Мы сразу вызываем функцию PrintText(), в которую передаём в качестве параметра результат работы тернарного оператора, который в свою очередь вызывает функцию IssAutch(). Но внимательные читатели наверняка заметили ещё одно изменение!

Обратите внимание, что в функции PrintText() тоже произошли изменения. До этого мы получали текст по ссылке, но теперь — по значению. Почему так? Если вы внимательно читали публикацию о передаче значений по ссылке и по значению, то должны помнить, что ссылка должна быть на область памяти. Но мы тут переменную не создали! Ещё ещё просто нет, по этому в данном случае мы можем принимать текст только по значению.

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

Циклы

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

#include <iostream>

using namespace std;

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int size = 7;
    short int temp[size] = {11, 18, 19, 20, 21, 20, 18};

    for(int i = 0; i < size; i++){
        cout << temp[i] << "; ";
    }
    
    return 0;
}

Что мы тут видим? Мы сразу создаём массив с длиной 7 символов, затем в цикле for выводим его. Из чего состоит этот цикл? Первым параметром идёт int i = 0; Это так называемый итератор цикла. Опять же, не пугайтесь умного названия. Выражаясь по Русски, это всего лишь счётчик. Переменная, которая записывает, сколько раз цикл отработал. Далее идёт i < size; Это выражение, которое определяет, до каких пор цикл будет работать. В нашем случае до тех пор, пока счётчик меньше общего количества элементов в массиве. Это тот самый условный оператор в миниатюре, который мы только что прошли. Далее i++. Это объяснить сложно, мы ещё инкременты не проходили. Но говоря простым языком, в этом, третьем, параметре, пишем действие, которое делается по завершении каждого шага цикла. В данном случае — мы прибавляем к счётчику единичку на самом деле тут можно было написать так: i = i + 1. И всё работало бы точно так же. Можете сами проверить.

Теперь давайте ещё раз проговорим, как работает цикл. У нас есть счётчик i и в выражении, втором параметре цикла, мы смотрим, он меньше количества элементов? Да, он меньше, значит выполняем шаг цикла. Т.е. то, что написано в фигурных скобках. После того, как все действия выполнены, мы возвращаемся в третий параметр, где написано, что мы прибавляем к счётчику единичку. Прибавили, смотрим условие. Если условие вернуло true — значит снова выполняем действия, описанные в фигурных скобках и снова возвращаемся. И так до тех пор, пока выражение не вернёт false. Как видим, логика работы цикла — очень похожа на условный оператор. С той разницей, что тут всё выполняется до тех пор, пока действие не вернёт false.

Давайте рассмотрим ещё один вариант цикла.

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int size = 7;
    short int i = 0;
    short int temp[size] = {11, 18, 19, 20, 21, 20, 18};

    while (i < size){
        cout << temp[i] << "; " << endl;
        i++;
    }
    
    return 0;
}

Чем он отличается? Тем, что в скобочках у нас осталось только само условие. тут нам надо самостоятельно заботиться о условии выхода из цикла. И это важно! Не забывайте, что в вашем цикле у вас всегда должно быть условие, когда цикл завершается! Иначе программа просто повиснет!

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

Пример не корректного вывода массива

Ключевые слова break и continue

И так, вот мы и познакомились с понятием условных операторов. Не волнуйтесь, если пока что ещё не до конца разобались с циклами. В будущем мы с ними ещё будем довольно плотно работать и вы сможете подтянуть знания.

И давайте вы подтяните знания на примере изучения ключевых слов, используемых с этими условными операторами. Сразу пример. Попробуйте разобраться, что тут происходит?

#include <iostream>

using namespace std;

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int size = 7;
    short int temp[size] = {11, 12, 13, 0, 15, 0, 17};

    for(int i = 0; i < size; i++){
        if(temp[i] == 0)
            continue;

        cout << temp[i] << "; " << endl;
    }
    
    return 0;
}

Итак, давайте разберёмся. Внутри цикла мы видим if, который проверяет элемент массива на ноль. Если значение равняется нулю — мы видим в теле всего одну строку, continue;. Что она делает? Это ключевое слово говорит, что текущую итерацию (шаг) цикла нужно прервать и переходить к следующему шагу. Однако, как мы сегодня узнали, если мы написали условный оператор без фигурных скобок — он распространяется только на одно действие. В данном случае — оператор continue. Т.е. если выражение вернуло false, — мы пропускаем этот оператор, то мы пишем элемент из массива. Таким образом в выводе мы пропускаем значения, равные нулю. 

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

Но что делать, если мы, к примеру, хотим завершить выполнение цикла, если в массиве есть ноль? Это мы можем сделать с помощью оператора break. Тогда наш код будет выглядеть вот так.

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int size = 7;
    short int temp[size] = {11, 12, 13, 0, 15, 0, 17};

    for(int i = 0; i < size; i++){
        if(temp[i] == 0)
            break;

        cout << temp[i] << "; " << endl;
    }
    
    return 0;
}

Мы уже знакомы с этим ключевым словом по switch, где мы так же выходили из ветвления при помощи break. таким образом, в данном случае, цикл у нас может завершиться в двух случаях.

  1. Если закончился массив и конструкция i < size вернула false
  2. if сработал, найдя нулевое значение и ключевое слово break завершило цикл

Пример работы в данном случае будет выглядеть так.

Пример завершения при нахождении нулевого значения

А теперь внимание, задача со звёздочкой! Попробуйте решить самостоятельно! Задача такая. Есть массив с числами. Ваша задача — вывести все чётные значения на экран. Но при этом, если в массиве найден ноль — цикл завершаем и дальнейшие элементы не выводим. Давайте для упрощения задачи — приведу основной код, до цикла.

#include <iostream>

using namespace std;

int main(){
    setlocale(LC_ALL, "ru_RU.UTF-8");
    short int size = 8;
    short int temp[size] = {11, 12, 13, 14, 15, 0, 17, 18};

    for(int i = 0; i < size; i++){
        /* напишите тут реализацию,
         * Которая будет выводить все чётные значения из массива
         * Но если получаем нулевое значение - завершаем работу программы
        */
    }
    
    return 0;
}

Пока что предлагайте Ваши варианты решения, а свой я опубликую в следующей публикации, посвящённой урокам по С++. Удачи, друзья! ;)

Публикация относится к тематической подборке: «Уроки C++»

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

Понравилась публикация? Поделись ей с друзьями!

Понравился сайт? Подпишьсь на нас в соцсетях!

Мы в TelegramМы ВконтактеМы в ТвиттерМы на фейсбукМы в одноклассниках
Опубликовать
Загрузка рекомендуемых публикаций