И, или, не
Условия из жизни редко бывают из одной части. Доставка бесплатная, если сумма от 3000 руб. и зона — город. Заказ на контроле, если он не оплачен или срочный. Для таких условий есть три логические операции.
&& — «и»
Ситуация. Бесплатная доставка — только для заказов от 3000 руб. в первую зону.
sum := 350000
zone := 1
if sum >= 300000 && zone == 1 {
fmt.Println("Доставка бесплатно")
} else {
fmt.Println("Доставка платная")
}stagiaire@lab:~/gocourse/day06/scratch$ go run .
Доставка бесплатноКак это читать. && соединяет два значения bool и даёт true, только если оба true. Хватит одного false — всё условие false: при zone := 2 доставка платная, какой бы ни была сумма.
|| — «или» и ! — «не»
|| (две вертикальные черты: в английской раскладке Shift и \) даёт true, если хотя бы одно значение true. ! перед значением меняет его на противоположное: !true — false.
paid := false
urgent := true
if !paid || urgent {
fmt.Println("Заказ на контроле")
}
fmt.Println(!paid, paid || urgent, paid && urgent)Заказ на контроле
true true falseВсе случаи для двух значений a и b:
a |
b |
a && b — «и» |
«или» | !a — «не» |
|---|---|---|---|---|
true |
true |
true |
true |
false |
true |
false |
false |
true |
false |
false |
true |
false |
true |
true |
false |
false |
false |
false |
true |
В столбце «или» — значение a || b.
! пишется вплотную к значению: !paid читается «не оплачен». Работает только с bool — !stock для числа не соберётся.
Попробуйте сейчас: четыре сочетания paid и urgent.
Цель: предсказать
!, «или» и&&для всех четырёх сочетаний и сверить с запуском.1. Наберите в
scratch/main.goпрограмму сpaidиurgentцеликом:✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { paid := false urgent := true if !paid || urgent { fmt.Println("Заказ на контроле") } fmt.Println(!paid, paid || urgent, paid && urgent) }2. Переберите все четыре сочетания
trueиfalseи перед каждым запуском предскажите вторую строку вывода:▶ Выполнитеcd ~/gocourse/day06/scratch go run .Готово, когда: на всех четырёх сочетаниях вторая строка вывода совпала с предсказанием и с таблицей выше.
Порядок операций и скобки
Как умножение выполняется раньше сложения, так ! выполняется раньше
&&, а && — раньше ||:
paid := true
urgent := false
vip := false
fmt.Println(paid || urgent && vip)
fmt.Println((paid || urgent) && vip)true
falseВ первой строке сначала urgent && vip — false, потом paid || false — true. Во второй скобки заставили сначала вычислить «или». Правило для
своего кода: если в условии есть и &&, и || — ставьте скобки, даже когда порядок и так верный. Читающему не придётся вспоминать таблицу старшинства.
Короткое вычисление
Ситуация. Товаров 12, коробок пока 0. Нужно предупредить, если на коробку приходится больше трёх товаров.
boxes := 0
items := 12
if items/boxes > 3 && boxes != 0 {
fmt.Println("Коробки переполнены")
}
fmt.Println("Готово")stagiaire@lab:~/gocourse/day06/scratch$ go run .
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.main()
/home/stagiaire/gocourse/day06/scratch/main.go:8 +0x9
exit status 2Последняя часть строки со стеком — +0x9 — зависит от сборки и версии компилятора: у вас там может быть другое число. Всё остальное, включая goroutine 1 [running]: и exit status 2, будет ровно такое.
Паника из дня 03: деление на переменную, в которой ноль. Проверка boxes != 0 есть, но стоит справа — до неё дело не дошло.
Поменяйте части местами:
boxes := 0
items := 12
if boxes != 0 && items/boxes > 3 {
fmt.Println("Коробки переполнены")
}
fmt.Println("Готово")ГотовоКак это читать. && и || вычисляют части слева направо и останавливаются, как только ответ ясен:
- у
&&левая частьfalse— ответfalse, правая не вычисляется; - у
||левая частьtrue— ответtrue, правая не вычисляется.
Здесь boxes != 0 — false, и деление справа не выполняется вовсе. Это называется коротким вычислением. Правило: проверка, которая защищает («не ноль», «есть товар»), стоит слева от того, что без неё сломается.
Попробуйте сейчас: что выведет программа?
Цель: для каждого
ifрешить, вычисляется ли правая часть, и сдать ответday06.q3.1. Блок ниже только для чтения — пройдите его в голове:
Код для чтения · разбираем, набирать не нужноboxes := 0 items := 12 code := 0 if boxes == 0 || items/boxes > 100 { code = code + 1 } if boxes > 0 && items/boxes > 3 { code = code + 10 } if items > 10 || items/boxes > 3 { code = code + 100 } fmt.Println("Код проверки:", code)Каждое сработавшее условие добавляет свою цифру: по числу видно, какие сработали. Для каждого
ifрешите, вычисляется ли правая часть. Если считаете, что программа упадёт, так и ответьте.2. Сдайте предсказание — до запуска:
▶ Выполните · выделенное замените своимcourse answer day06.q3 ЧИСЛОВместо
ЧИСЛО— ваш ответ: число после «Код проверки:».3. Теперь проверьте себя запуском. Наберите в
scratch/main.goту же программу целиком:✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { boxes := 0 items := 12 code := 0 if boxes == 0 || items/boxes > 100 { code = code + 1 } if boxes > 0 && items/boxes > 3 { code = code + 10 } if items > 10 || items/boxes > 3 { code = code + 100 } fmt.Println("Код проверки:", code) }▶ Выполнитеcd ~/gocourse/day06/scratch go run .Готово, когда: ответ сдан до запуска, и запуск его подтвердил;
в
course check 06пункт «Что выведет: короткое вычисление && и ||» (q3) отмечен ✔.
Вложенные if или &&
Условие «сумма от 3000 руб. и первая зона» можно записать вложенным if: внутри блока if sum >= 300000 { ещё один if zone == 1 {. Результат тот же, что у одного if sum >= 300000 && zone == 1 {, но && короче и читается одной фразой — берите его, когда действие одно. Вложенный if оправдан, когда у внутренней проверки своё else, которое имеет смысл только при выполненном внешнем условии: при сумме от 3000 руб. первая зона — доставка бесплатно, остальные — скидка на доставку, а при меньшей сумме — тариф. Через && пришлось бы дважды писать sum >= 300000. Больше двух уровней вложенности читать трудно — тогда цепочку перестраивают.
go vet и подозрительные условия
Ситуация. Хотели «зона не 1 и не 2», а написали «или»:
zone := 2
if zone != 1 || zone != 2 {
fmt.Println("Зона не 1 и не 2")
}stagiaire@lab:~/gocourse/day06/scratch$ go run .
Зона не 1 и не 2
stagiaire@lab:~/gocourse/day06/scratch$ go vet .
main.go:7:5: suspect or: zone != 1 || zone != 2Программа собралась и напечатала неправду. Условие истинно при любой зоне: 2 не равно 1, 1 не равно 2, а любое другое число не равно ни тому, ни другому. go vet (день 03) такие условия узнаёт: suspect or — «подозрительное или». Верно — zone != 1 && zone != 2. Смысл порогов
vet не проверяет, но самые частые описки в && и || находит.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
syntax error: unexpected name and, expected { |
написано слово and |
&&; «или» — две вертикальные черты, «не» — ! |
invalid operation: sum >= 300000 & zone == 1 (mismatched types untyped bool and untyped int) |
один знак & вместо двух |
&& |
invalid operation: operator ! not defined on stock (variable of type int) |
! перед числом |
сравнение: stock == 0 |
invalid operation: 1000 <= weight <= 5000 (mismatched types untyped bool and untyped int) |
двойное сравнение, как в математике | две части через &&: weight >= 1000 && weight <= 5000 |
panic: runtime error: integer divide by zero в строке с && |
деление стоит левее проверки на ноль | проверку на ноль — левой частью |
go vet: suspect or:, дальше два != через «или» (две вертикальные черты) — как в примере выше |
условие истинно всегда | && вместо «или» |
Попробуйте сейчас: go vet и двойное сравнение.
Цель:
go vet .находит «подозрительное или» и замолкает после исправления; двойное сравнение переписано через&&.1. Наберите в
scratch/main.goпрограмму сzone != 1целиком — тогда и номер строки в сообщенииvetсовпадёт:✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { zone := 2 if zone != 1 || zone != 2 { fmt.Println("Зона не 1 и не 2") } }Запустите:
▶ Выполнитеcd ~/gocourse/day06/scratch go vet .2. Исправьте условие и запустите
go vet .ещё раз — он ничего не напечатает.3. Двойное сравнение попробуйте тоже — наберите в
scratch/main.go:✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { weight := 3700 if 1000 <= weight <= 5000 { fmt.Println("средний") } }▶ Выполнитеgo run .Прочитайте сообщение и перепишите условие через
&&.4. Новые сообщения — в
~/errors.md.Готово, когда: первый
go vet .напечаталsuspect or: …, второй — ничего; на двойном сравнении было сообщениеinvalid operation: 1000 <= weight <= 5000 …, а вариант с&&собирается; сообщения записаны в~/errors.md(пункт проверкиt_vet).
Дальше: switch — выбор из многих вариантов — course next