1. Вступ
Уявіть, що ви пишете програму-«вахтера» для бібліотеки. Так, знову бібліотека: програмісти люблять бібліотеки, бо там тихо й ніхто не відволікає від налагодження. Вахтер має вирішити, чи може відвідувач зайти всередину і чи можна йому брати книги. Одного порівняння на кшталт age >= 18 майже ніколи не вистачає. Зазвичай потрібні кілька умов одразу: «є читацький квиток», «немає заборгованості», «або дорослий, або прийшов із дорослим». Саме тут логічні оператори стають вашим швейцарським ножем.
Важливо зрозуміти базову ідею: порівняння та перевірки дають нам маленькі шматочки типу Bool (тобто true/false), а &&, ||, ! дозволяють скласти з цих шматочків складніший зміст — майже як із кубиків LEGO, тільки без ризику наступити на них уночі.
2. Оператор &&: логічне «І»
Коли ви читаєте умову A && B, це звучить як «A і B». Але в програмуванні «і» має точне значення: результат буде true лише тоді, коли обидві частини — true. Якщо хоча б одна частина false, увесь вираз стає false. Тут логіка залізобетонна: «пройти можна, якщо є квиток і немає боргів». Якщо квиток є, але борги теж є, бібліотека, на жаль, сьогодні сувора.
У Swift && використовують для об’єднання двох виразів типу Bool. Зазвичай кожну частину ви отримуєте через порівняння, наприклад age >= 18, або через уже готову булеву змінну, наприклад hasCard.
Міні-приклад: «у діапазоні від 1 до 10»
let x = Int(readLine() ?? "") ?? 0
let isInRange = x >= 1 && x <= 10
print("isInRange = \(isInRange)") // наприклад: isInRange = true
Тут дві перевірки працюють разом: число має бути і не меншим за 1, і не більшим за 10.
Таблиця істинності для &&
Щоб не вгадувати результат, логіка любить таблиці істинності. Це проста таблиця, де ми перебираємо всі варіанти true/false.
| A | B | A && B |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Ця таблиця корисна тим, що допомагає перевірити власні міркування. Якщо вам здається, що true && false має бути true, ні — це просто ваш мозок утомився.
3. Оператор ||: логічне «АБО»
Оператор || читається як «або», але не як розмовне «або… або…», де інколи мають на увазі вибір лише одного варіанта. У програмуванні A || B означає: «A істинне або B істинне, або обидва істинні». Результат буде false лише тоді, коли обидві частини false.
Це зручно, коли підходить кілька варіантів. Наприклад: «вхід дозволено, якщо сьогодні субота або неділя», або «команда допустима, якщо це help або exit».
Міні-приклад: «команда — одна з дозволених»
let command = readLine() ?? ""
let isServiceCommand = command == "help" || command == "exit"
print("isServiceCommand = \(isServiceCommand)") // наприклад: true
Таблиця істинності для ||
| A | B | A || B |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Найважливіший висновок: || дає false лише в одному випадку — коли все погано одразу.
4. Оператор !: логічне «НЕ»
Іноді ви вже обчислили умову, але вам потрібен протилежний результат. Наприклад, у вас є hasDebt («є борг»), а перевірити треба «боргу немає». Можна, звісно, писати hasDebt == false, але це читається тяжкувато: «є борг дорівнює хибність» — звучить радше як філософія, а не код. Для цього й існує оператор заперечення !.
!A означає «не A». Якщо A було true, стане false. Якщо було false, стане true. Дуже прямо — як охоронець у бібліотеці.
Міні-приклад: «немає заборгованості»
let debtText = readLine() ?? ""
let hasDebt = debtText == "yes"
let hasNoDebt = !hasDebt
print("hasDebt = \(hasDebt)") // наприклад: hasDebt = true
print("hasNoDebt = \(hasNoDebt)") // наприклад: hasNoDebt = false
Таблиця істинності для !
| A | !A |
|---|---|
|
|
|
|
5. Склеюємо умови: спочатку «цеглинки», потім «будинок»
Перш ніж перейти до важливої особливості && і ||, варто обговорити стиль, який справді береже нерви. Новачки часто намагаються одразу писати гігантські умови прямо в if: в один рядок, із п’ятьма порівняннями та двома запереченнями. Компілюється це без проблем, але читати потім важко навіть через кілька днів.
Гарний прийом — спочатку обчислити маленькі булеві «цеглинки» в let, а потім зібрати з них підсумкову умову. Це не лише питання «красивості», а й спосіб не переплутати зміст.
Міні-приклад: перевірка допуску до бібліотеки
let age = Int(readLine() ?? "") ?? 0
let cardText = readLine() ?? "" // очікуємо "yes" або "no"
let debtText = readLine() ?? "" // очікуємо "yes" або "no"
let isAdult = age >= 18
let hasCard = cardText == "yes"
let hasDebt = debtText == "yes"
let canEnter = isAdult && hasCard && !hasDebt
print("canEnter = \(canEnter)") // наприклад: canEnter = true
Зверніть увагу: тут ми вже використали && кілька разів поспіль — це нормально, бо всі частини мають один тип, а зміст читається як «дорослий, із квитком і без боргів».
6. Коротка перевірка: short-circuit у && і ||
Зараз буде момент, який робить && і || не просто логічними операторами, а ще й інструментом безпеки. У Swift, як і в багатьох мовах, && і || працюють із короткою перевіркою: інколи права частина взагалі не обчислюється, бо відповідь уже видно з лівої. Це важливо не лише для швидкості, а й для того, щоб не виконувати небезпечні дії на кшталт ділення на нуль.
Спочатку вираз читається й обчислюється зліва направо, і Swift намагається зберігати саме такий порядок.
Для && це означає: якщо зліва вже false, то весь вираз не може стати true, отже праворуч навіть не треба дивитися. Для || навпаки: якщо зліва вже true, то весь вираз уже true, і перевіряти праворуч немає сенсу.
У документації та пропозиціях щодо розвитку Swift це підкреслюють навіть опосередковано — зокрема там, де говорять про схожі, але не такі самі логічні операції. Наприклад, у пропозиції щодо SIMD-масок прямо сказано, що спеціальні булеві операції на масках не short-circuit, «на відміну від && і ||».
Short-circuit у &&: «запобіжник» ставимо ліворуч
Найкласичніший «запобіжник» — захист від ділення на нуль. У Swift ділення на нуль призводить до аварійного завершення програми (runtime error). Тому, якщо праворуч у виразі є потенційно небезпечна операція, спочатку зліва перевіряємо, що вона безпечна.
let a = Int(readLine() ?? "") ?? 0
let b = Int(readLine() ?? "") ?? 0
let isSafeAndBig = a != 0 && (b / a) > 2
print("isSafeAndBig = \(isSafeAndBig)") // за a == 0 програма НЕ впаде
Чому не впаде? Тому що при a == 0 ліва частина a != 0 дає false, а отже false && що завгодно все одно false. І Swift навіть не полізе обчислювати (b / a) > 2.
Запам’ятайте це як практичне правило: якщо праворуч є небезпечна частина, у && ліворуч має стояти запобіжник.
Short-circuit у ||: «швидкий успіх» ставимо ліворуч
У || логіка дзеркальна. Якщо ви вже знаєте, що умова істинна, немає сенсу перевіряти другий варіант. На практиці це корисно, коли праворуч у вас важча перевірка, наприклад довге порівняння або потенційно ризикований доступ.
Ми поки не вміємо красиво показати «важку операцію» без функцій і замикань; до них дійдемо значно пізніше. Але зміст можна прочитати так: якщо перший варіант підходить, другий уже не потрібен. Це і є коротка перевірка, яка в || працює тому, що true || що завгодно завжди true.
7. Приклад: «Вахтер бібліотеки»
Давайте акуратно з’єднаємо &&, ||, ! в один невеликий сценарій. Поки без циклів — вони будуть завтра, — тому програма «одноразова»: зчитала введення, ухвалила рішення, вивела результат. Але навіть так вона вже схожа на справжню валідацію.
Умови такі, спеціально життєві, щоб мозок не нудьгував:
- Увійти до бібліотеки можна, якщо є читацький квиток і немає заборгованості.
- Взяти книгу можна, якщо можна увійти, і при цьому або ви дорослий, або прийшли з дорослим супроводжуючим.
Ми спеціально не будемо змішувати && і || в одному величезному рядку — натомість винесемо проміжну умову в окремий let. Так і читається простіше, і менше шансів заплутатися. Про змішування та дужки поговоримо в наступній лекції — там буде окрема магія змісту.
let age = Int(readLine() ?? "") ?? 0
let cardText = readLine() ?? "" // очікуємо "yes" або "no"
let debtText = readLine() ?? "" // очікуємо "yes" або "no"
let adultWithYouText = readLine() ?? "" // очікуємо "yes" або "no"
let hasCard = cardText == "yes"
let hasDebt = debtText == "yes"
let isAdult = age >= 18
let hasAdultWithYou = adultWithYouText == "yes"
let canEnter = hasCard && !hasDebt
let isAdultOrWithAdult = isAdult || hasAdultWithYou
let canBorrow = canEnter && isAdultOrWithAdult
print("canEnter = \(canEnter)") // наприклад: canEnter = true
print("canBorrow = \(canBorrow)") // наприклад: canBorrow = false
Зверніть увагу на !hasDebt. У реальному коді таке трапляється постійно: ми зберігаємо факт «є борг», але перевіряємо «боргу немає». І ! робить це максимально коротко й чесно.
Якщо вам хочеться запитати: «А чому ми просимо вводити саме yes і no, а не “Так/Ні” чи “YES”?» — тому що нормалізація введення користувача, тобто обрізання пробілів і приведення регістру, буде пізніше. Зараз наша мета — логіка, а не боротьба з людським фактором.
Таблиці істинності як інструмент налагодження
Таблиця істинності звучить трохи нудно, але на практиці це майже міні-налагоджувач для голови. Коли ви пишете складну умову, ви фактично будуєте маленьку логічну схему. І якщо схема неправильна, програма «правильно» робитиме неправильні речі, а це найпідступніший тип помилок.
Корисний спосіб самоперевірки такий: берете ваші булеві цеглинки і подумки проганяєте 2–4 характерні сценарії. Наприклад, для нашої бібліотеки:
- hasCard = false, hasDebt = false → вхід? Ні, бо немає квитка.
- hasCard = true, hasDebt = true → вхід? Ні, бо є борг.
- canEnter = true, isAdult = false, hasAdultWithYou = true → взяти книгу? Так, бо поруч є дорослий.
- canEnter = true, isAdult = false, hasAdultWithYou = false → взяти книгу? Ні.
І якщо десь ваша відповідь за змістом не збігається з тим, що дає формула з &&, ||, !, значить, формула описує не ті правила, які ви хотіли закласти.
8. Типові помилки під час роботи з &&, ||, !
Помилка №1: плутати людське “або” з програмним ||.
У розмовній мові «чай або кава» інколи означає «оберіть щось одне». У програмуванні A || B означає «достатньо одного істинного», і якщо істинні обидва — це теж нормально. Через це умова може виявитися надто «дозвільною», і програма почне пропускати випадки, які ви не планували.
Помилка №2: забувати, що ! стосується одного булевого значення.
Сам по собі ! перевертає один Bool. Новачки інколи думають, що ! «скасовує» весь умовний вираз навколо, хоча насправді він застосовується до конкретного шматочка. Якщо ви хотіли заперечити результат усієї перевірки, краще спочатку зберегти її в змінну, а потім написати let isNotAllowed = !isAllowed. Так зміст буде видно одразу, і ви не будете сперечатися із самим собою про те, що саме заперечується.
Помилка №3: ставити «запобіжник» праворуч у &&.
Якщо праворуч у вас потенційно небезпечний вираз, наприклад ділення, остача або доступ до чогось, а перевірку безпеки ви ставите після нього, short-circuit не допоможе. У && безпечна перевірка має стояти ліворуч, тому що права частина може й не виконатися, а ліва виконується завжди.
Помилка №4: перетворювати умову на «простирадло», яке неможливо прочитати.
Навіть якщо умова формально правильна, вона може бути непридатною для читання. Коли ви бачите в if десять операторів підряд, мозок починає вдавати, що це не його проблема. Ліки просте: робіть let isAdult = …, let hasCard = …, let canEnter = …. Це не «зайві рядки», а спосіб зробити код зрозумілим.
Помилка №5: намагатися використовувати &&/|| не з Bool.
У Swift умова має бути саме Bool, і оператори &&/|| очікують булеві значення ліворуч і праворуч. Не можна писати «0 означає false, 1 означає true» як у деяких інших мовах. Тому завжди спочатку робіть порівняння (x > 0, command == "exit"), а вже потім об’єднуйте результати логікою.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ