JavaRush /Курсы /C++ SELF /Логические операции: &&, ||, !, короткое замыкани...

Логические операции: &&, ||, !, короткое замыкание

C++ SELF
3 уровень , 3 лекция
Открыта

1. Введение

Когда вы впервые видите if (x > 0), кажется, что этого достаточно: есть проверка — есть решение. Но в реальной задаче условие почти никогда не бывает «одно сравнение и всё».

Чаще оно звучит как человеческая фраза из нескольких требований: «число должно быть в диапазоне», «команда может называться так или так», «делить можно только если делитель не ноль», «символ можно брать только если строка не пустая».

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

Чтобы примеры были связными, будем развивать мини‑приложение SmartCalc: программа читает команду ("add", "sub", "mul", "div", "mod") и два целых числа, а затем либо выводит результат, либо сообщает, почему действие выполнить нельзя. Циклы мы ещё не проходили, поэтому программа делает одну операцию и заканчивает.

2. && — логическое И

Оператор && читается как «И». Он нужен, когда условие должно быть истинным сразу по нескольким требованиям. Типичный человеческий пример: «вход разрешён, если у тебя есть билет и тебе уже 18».

Правило простое: выражение A && B истинно только тогда, когда истинны оба выражения: и A, и B. Если хотя бы одно ложно — весь результат ложный. При этом && умеет «лениться» и иногда даже не вычислять правую часть — это и называется короткое замыкание.

Таблица истинности для &&

A
B
A && B
false
false
false
false
true
false
true
false
false
true
true
true

Пример 1: проверка диапазона

Проверка «число от 1 до 10 включительно» почти всегда выглядит так:

#include <iostream>

int main() {
    int x = 0;
    std::cin >> x;

    if (x >= 1 && x <= 10) {
        std::cout << "ok\n";        // ok
    } else {
        std::cout << "not ok\n";    // not ok
    }
}

Здесь x >= 1 — первая часть, x <= 10 — вторая. В диапазоне должны быть выполнены обе, иначе смысл «от…до…» ломается.

Пример 2: «валидация» для SmartCalc

В нашем SmartCalc есть операции "div" и "mod", которым жизненно важно, чтобы делитель не был нулём. Пока что просто зафиксируем идею: перед «опасной операцией» мы хотим условие вида «команда — деление И делитель не ноль».

#include <iostream>
#include <string>

int main() {
    std::string cmd;
    int a = 0, b = 0;
    std::cin >> cmd >> a >> b;

    if (cmd == "div" && b != 0) {
        std::cout << (a / b) << '\n';   // безопасно
    }
}

Пока здесь ещё нет короткого замыкания в полной красе (оно проявится, когда справа будет стоять что-то, что может упасть). Но уже видно «смысловую склейку»: обе части должны быть правдой.

3. || — логическое ИЛИ

Оператор || читается как «ИЛИ». Он нужен, когда условие допускает несколько вариантов: «можешь войти, если у тебя есть пропуск или ты в списке гостей».

Правило: A || B истинно, если истинно хотя бы одно из выражений. Если оба ложны — результат ложный. И да, || тоже умеет короткое замыкание: если слева уже true, справа можно не считать.

Таблица истинности для ||

A
B
A || B
false
false
false
false
true
true
true
false
true
true
true
true

Пример 1: несколько вариантов команды

Допустим, мы хотим, чтобы пользователь мог вводить и короткую команду, и длинную. Например: "add" и "+".

#include <iostream>
#include <string>

int main() {
    std::string cmd;
    std::cin >> cmd;

    if (cmd == "add" || cmd == "+") {
        std::cout << "addition selected\n"; // addition selected
    }
}

Это ровно «один из вариантов». Без || пришлось бы писать два отдельных if или городить странные конструкции.

Пример 2: «да/нет» с несколькими ответами

Очень типичная практика: принимать "y" и "yes" как одно и то же.

#include <iostream>
#include <string>

int main() {
    std::string ans;
    std::cin >> ans;

    if (ans == "y" || ans == "yes") {
        std::cout << "confirmed\n";        // confirmed
    } else {
        std::cout << "not confirmed\n";    // not confirmed
    }
}

Важный момент: логическое «или» — это не «сложение строк» и не «побитовое или». Мы пока работаем только с логикой: значениями bool, полученными из сравнений.

4. ! — логическое НЕ

Оператор ! читается как «НЕ». Он инвертирует логическое значение: true превращает в false, а false — в true.

При этом ! — оператор очень «прилипчивый»: он относится к ближайшему выражению справа. Поэтому в составных условиях почти всегда нужны скобки, иначе легко инвертировать «не то». Простое правило: если после ! стоит что-то длиннее одного сравнения, ставьте скобки и спите спокойно.

Пример 1: «x не равен нулю» через !

Можно написать так:

#include <iostream>

int main() {
    int x = 0;
    std::cin >> x;

    bool ok = !(x == 0);
    std::cout << ok << '\n';   // 0 если x==0, иначе 1
}

Это не «лучше» и не «хуже», чем x != 0, но помогает почувствовать механику: x == 0 даёт bool, потом ! переворачивает.

Пример 2: запрет пустой строки перед индексированием

Мы уже знаем, что s[0] опасно, если строка пустая. Условие «строка НЕ пустая» можно выразить так:

#include <iostream>
#include <string>

int main() {
    std::string s;
    std::getline(std::cin, s);

    if (!(s.size() == 0)) {
        std::cout << s[0] << '\n';  // печатаем первый символ
    }
}

Да, можно писать и s.size() != 0. Но форма с ! полезна, когда вы хотите инвертировать не одно сравнение, а целое составное условие — и вот там скобки уже становятся обязательными.

5. Короткое замыкание

Короткое замыкание звучит как название рок‑группы, но на деле это очень практичное правило вычисления логических выражений. Смысл в том, что && и || вычисляются слева направо и иногда «останавливаются» раньше, потому что результат уже понятен.

Для A && B всё решается так: если A оказалось false, то весь результат точно false, и вычислять B уже нет смысла. Для A || B наоборот: если A оказалось true, то весь результат точно true, и вычислять B уже не нужно.

Это поведение не просто «оптимизация», а часть языка: вы можете на него рассчитывать, и именно благодаря этому мы можем писать защитные условия.

Очень полезно думать о коротком замыкании как о дисциплине «сначала проверка безопасности — потом опасное действие». То есть слева ставим то, что гарантирует, что справа мы не сделаем ерунду.

Мини-схема: как && принимает решение

flowchart TD
    A["Вычислить A"] --> B{A == true?}
    B -- "нет" --> C["Результат false, B не вычисляем"]
    B -- "да" --> D["Вычислить B"]
    D --> E["Результат = B"]

6. Защитные условия: деление и индексирование

Сейчас мы применим короткое замыкание так, как его применяют в «настоящем» коде: чтобы не делить на ноль и не лезть за границы строки.

Пример 1: безопасное деление через &&

Если написать так, будет плохо:

// так делать НЕ надо
if (a / b > 2 && b != 0) { /* ... */ }

Потому что деление выполнится раньше, чем проверка b != 0. Правильный порядок такой: сначала проверка, потом деление. И короткое замыкание гарантирует, что деления не будет, если b == 0.

#include <iostream>

int main() {
    int a = 0, b = 0;
    std::cin >> a >> b;

    if (b != 0 && (a / b > 2)) {
        std::cout << "a/b > 2\n";           // безопасно
    } else {
        std::cout << "no or unsafe\n";      // no or unsafe
    }
}

Здесь, если b == 0, левая часть b != 0 — ложь, и правая часть (a / b > 2) даже не будет вычисляться. Это не магия, это «ленивость», которая экономит вам падение программы.

Пример 2: безопасный доступ к первому символу строки

С std::string история такая же: s[0] допустимо только если s.size() > 0. Ставим проверку слева:

#include <iostream>
#include <string>

int main() {
    std::string s;
    std::getline(std::cin, s);

    if (s.size() > 0 && s[0] == '#') {
        std::cout << "comment line\n";  // comment line
    } else {
        std::cout << "not a comment\n"; // not a comment
    }
}

Если строка пустая, s.size() > 0 даст false, и выражение s[0] == '#' не вычислится. То есть мы «прикрыли» опасный доступ простой проверкой.

7. SmartCalc: условия из нескольких проверок

Пора собрать мини‑версию SmartCalc, где && и || работают вместе: || — чтобы распознавать несколько вариантов команды, && — чтобы добавлять безопасность (делитель не ноль), а ! — чтобы удобно обрабатывать «неподходящие» случаи.

Сразу договоримся о формате ввода: пользователь вводит команду и два числа. Например: "div" 10 2. Программа делает одну операцию и завершает работу.

Шаг 1: распознаём команды синонимами (||)

#include <iostream>
#include <string>

int main() {
    std::string cmd;
    int a = 0, b = 0;
    std::cin >> cmd >> a >> b;

    if (cmd == "add" || cmd == "+") {
        std::cout << (a + b) << '\n';   // например: 7
    }
}

В таком стиле можно добавить "sub"/"-", "mul"/"*". Пока оставим одну ветку, чтобы не разрасталось.

Шаг 2: добавляем деление с защитой (&& + короткое замыкание)

#include <iostream>
#include <string>

int main() {
    std::string cmd;
    int a = 0, b = 0;
    std::cin >> cmd >> a >> b;

    if (cmd == "div" || cmd == "/") {
        if (b != 0) {
            std::cout << (a / b) << '\n';       // 5 при вводе: div 10 2
        } else {
            std::cout << "division by zero\n";  // division by zero
        }
    }
}

Здесь мы пока сделали «двухэтажный» if: внешний выбирает команду, внутренний проверяет безопасность. Это нормально и читаемо.

Но иногда хочется записать «в одну строку», и тут как раз пригодится &&:

#include <iostream>
#include <string>

int main() {
    std::string cmd;
    int a = 0, b = 0;
    std::cin >> cmd >> a >> b;

    if ((cmd == "div" || cmd == "/") && b != 0) {
        std::cout << (a / b) << '\n';   // безопасно: деление только если b!=0
    }
}

Смысл читается почти как по‑русски: «если команда "div" ИЛИ "/", И при этом b не ноль — делим».

Шаг 3: ! как «отрицание фильтра»

Иногда легче написать «если команда НЕ распознана — вывести ошибку». Для этого делаем булеву переменную, а потом используем !. Это повышает читаемость: условие получает имя, а не превращается в кашу.

#include <iostream>
#include <string>

int main() {
    std::string cmd;
    std::cin >> cmd;

    bool known = (cmd == "add" || cmd == "+" || cmd == "div" || cmd == "/");
    if (!known) {
        std::cout << "unknown command\n";    // unknown command
    }
}

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

Как не превратить условие в ребус: даём проверкам имена

Даже когда вы уже умеете && и ||, есть опасность: написать одно гигантское условие, которое технически верно, но психологически нечитабельно. Поэтому хороший приём — выносить части условия в bool-переменные с говорящими именами.

Важно, что это не «лишний код», а способ документировать намерение. Компилятор с радостью это оптимизирует, а вам проще читать.

Пример: читабельная проверка деления в SmartCalc

#include <iostream>
#include <string>

int main() {
    std::string cmd;
    int a = 0, b = 0;
    std::cin >> cmd >> a >> b;

    bool is_div = (cmd == "div" || cmd == "/");
    bool safe = (b != 0);

    if (is_div && safe) {
        std::cout << (a / b) << '\n';       // например: 3
    } else if (is_div && !safe) {
        std::cout << "division by zero\n";  // division by zero
    }
}

Здесь условие не стало «умнее», но стало понятнее: вы глазами видите смысл, а не разгадываете набор символов.

8. Типичные ошибки при работе с &&, ||, ! и коротким замыканием

В логических операциях большинство проблем — не из-за «сложной математики», а из-за того, что человек думает фразой на русском, а записывает её символами, и где-то по дороге теряется смысл.

Ошибка №1: перепутать && и || в диапазонах.
Когда проверяют «x от 1 до 10», новички иногда пишут x >= 1 || x <= 10. Такое условие почти всегда истинно: любое число либо не меньше 1, либо не больше 10. Для диапазона нужно именно «и»: x >= 1 && x <= 10, потому что оба ограничения должны выполняться одновременно.

Ошибка №2: поставить защитную проверку справа и получить падение.
Фраза «делим, если b не ноль» должна превратиться в код, где b != 0 стоит слева от деления: b != 0 && (a / b > 2). Если написать наоборот — (a / b > 2) && b != 0 — вы сначала делите, а потом проверяете, что делить было нельзя. Короткое замыкание не спасает, если опасность стоит слева.

Ошибка №3: ждать, что обе части всегда вычисляются.
Иногда в правой части && или || люди случайно прячут что-то «важное» (например, действие, которое они хотели выполнить всегда). Но короткое замыкание может не дать правой части выполниться. Поэтому логические операторы — не место для действий «на всякий случай». Пусть в A && B и A || B будут именно проверки, а действия — внутри тела if.

Ошибка №4: злоупотреблять ! без скобок и инвертировать не то.
Если вы пишете !x == 0, то это обычно не то, что вы хотели (и читается это плохо). Используйте простое правило: если вы хотите инвертировать сравнение — пишите !(x == 0) или используйте прямой оператор !=. А если инвертируете составное условие — скобки обязательны, иначе очень легко «перевернуть только половину смысла».

Ошибка №5: написать условие-«макаронину» и самому же в нём ошибиться.
Когда условие становится длинным, увеличивается шанс перепутать скобки, забыть часть проверки или поставить не тот оператор. Хорошая привычка: вынести части условия в bool-переменные с именами is_div, has_input, safe, in_range. Это не «болтовня», а способ сделать код самодокументируемым и снизить шанс ошибки в логике.

1
Задача
C++ SELF, 3 уровень, 3 лекция
Недоступна
Термостат офиса
Термостат офиса
1
Задача
C++ SELF, 3 уровень, 3 лекция
Недоступна
Ответ пользователя
Ответ пользователя
1
Задача
C++ SELF, 3 уровень, 3 лекция
Недоступна
Делимость без риска
Делимость без риска
1
Задача
C++ SELF, 3 уровень, 3 лекция
Недоступна
Тег или текст
Тег или текст
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Сергей Уровень 22
20 июня 2026
А зачем в последней задаче messageLine.size() > 0 &&? Мы ведь и так проверяем строку на .empty()
Hissazul Уровень 3
20 апреля 2026
Intellij1@if (denominator == 0) { std::cout << "DIVISION_BY_ZERO\n"; } else if (denominator != 0 && numerator % denominator == 0) { // в данном примере, конкретно в этой ветке программы, замыкание не сработает, так как это ветвление при denominator == 0 не выполнится никогда. Замыкание в некотором роде будет при срабатывании начального условия if (denominator == 0). std::cout << "DIVISIBLE " << numerator / denominator << '\n'; } else { std::cout << "NOT_DIVISIBLE\n"; }