Логические операторы в си
Логические операторы
Логические операторы – это операторы, которые принимают в качестве аргументов логические значений (ложь или истину) и возвращают логическое значение. Как и обычные операторы, они могут быть одноместными (унарными, т.е. принимать один аргумент), двуместными (бинарные, принимают два аргумента), трёхместными и т.д.
Особенностью языка си является то, что в нём нет типа, хранящего булево значение (ложь или истину). В си ложью (логическим нулём) считается целочисленный 0, а любое ненулевое целое будет логической истиной. Например
#include <conio.h> #include <stdio.h> void main() { char boolValue = -71; if (boolValue) { printf("boolValue is true"); } else { printf("boolValue is false"); } _getch(); }
Логические значения обычно порождаются операторами сравнения (==, !=, >, <, >=. <=).
В языке си представлено три логических оператора: И, ИЛИ и НЕ. Начнём с самого простого
Логическое отрицание
Оператор НЕ (NOT) используется для того, чтобы инвертировать значение аргумента. Т.е., если ему передали истину, то он вернёт ложь, если получил ложь в качестве аргумента, то вернёт истину.
X | NOT X |
---|---|
0 | 1 |
1 | 0 |
В си отрицание представлено оператором !. Например
#include <conio.h> #include <stdio.h> void main() { int i = 0; if (i) { printf("i is true\n"); } if (!i) { printf("i is not true\n"); } if (!!i) { printf("i is not not true\n"); } if (!!!i) { printf("i is not not not true\n"); } _getch(); }
Как и в обычной логике, здесь действует закон двойного отрицания – отрицание отрицания можно опустить.
Логическое И
Оператор И (AND, логическое умножение) возвращает истину тогда и только тогда, когда оба аргумента являются истиной.
X | Y | X AND Y |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
В си логическое умножение представлено оператором &&. Например, задача – в кружок военных спейсмаринов допускаются только совершеннолетние граждане мужского пола. То есть, претендентом может стать только тот, для которого одновременно два условия являются истиной
#define _CRT_SECURE_NO_WARNINGS #include <conio.h> #include <stdio.h> void main() { char gender; unsigned int age; printf("Enter gender ('M' or 'F')\n"); scanf("%c", &gender); printf("Enter age\n"); scanf("%u", &age); if (gender == 'M' && age > 17) { printf("Wellcome"); } else { printf("Go away"); } _getch(); }
Оператор И может применяться последовательно к нескольким аргументам. Для него действует ассоциативный и коммутативный законы. Усовершенствуем программу, будем также вводить рост:
#define _CRT_SECURE_NO_WARNINGS #include <conio.h> #include <stdio.h> void main() { char gender; unsigned int age; unsigned int height; printf("Enter gender ('M' or 'F')\n"); scanf("%c", &gender); printf("Enter age\n"); scanf("%u", &age); printf("Enter height\n"); scanf("%u", &height); if (gender == 'M' && age > 17 && height >= 180) { printf("Wellcome"); } else { printf("Go away"); } _getch(); }
Также условие могло быть записано
(gender == 'M' && age > 17) && height >= 180
или
gender == 'M' && (age > 17 && height >= 180)
или
(age > 17 && height >= 180) && gender == 'M'
Логическое ИЛИ
Оператор логическое ИЛИ (логическое сложение, OR) истинен тогда, когда истиной является хотя бы один его аргумент.
X | Y | X OR Y |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
В си ИЛИ представлен оператором ||. Например, усовершенствуем программу: теперь пол можно вводить как большой, так и маленькой буквой
#define _CRT_SECURE_NO_WARNINGS #include <conio.h> #include <stdio.h> void main() { char genderInput; char gender; unsigned int age; unsigned int height; printf("Enter gender ('M' or 'F')\n"); scanf("%c", &genderInput); printf("Enter age\n"); scanf("%u", &age); printf("Enter height\n"); scanf("%u", &height); if (genderInput == 'M' || genderInput == 'm') { gender = 1; } else { gender = 0; } if ((age > 17 && height >= 180) && gender) { printf("Wellcome"); } else { printf("Go away"); } _getch(); }
Как и в случае оператора И, ИЛИ коммутативен и ассоциативен.
Операторы можно перемешивать друг с другом, создавая сложные операторы
#define _CRT_SECURE_NO_WARNINGS #include <conio.h> #include <stdio.h> void main() { char gender; unsigned int age; unsigned int height; printf("Enter gender ('M' or 'F')\n"); scanf("%c", &gender); printf("Enter age\n"); scanf("%u", &age); printf("Enter height\n"); scanf("%u", &height); if ((age > 17 && height >= 180) && (gender == 'M' || gender == 'm')) { printf("Wellcome"); } else { printf("Go away"); } _getch(); }
Стоит только помнить о том, что оператор отрицания имеет больший приоритет, чем И или ИЛИ, поэтому будет выполняться в первую очередь. Если может случиться ситуация, когда порядок выполнения не ясен, определите его с помощью скобок.
Пример: закон де-Моргана. Чтобы сменить И на ИЛИ (или наоборот), необходимо инвертировать значения всех операндов, заменить И на ИЛИ (или ИЛИ на И) и инвертировать конечный результат. В случае с нашим условием
(age > 17 && height >= 180) && (gender == 'M' || gender == 'm')
Рассмотрим сначала кусок
(age > 17 && height >= 180)
Меняем все значения на обратные
(!(age > 17) && !(height >= 180))
заменяем оператор && на ||
(!(age > 17) || !(height >= 180))
и инвертируем ответ
!(!(age > 17) || !(height >= 180))
Как видим, результат тот же. Очевидно, что
!(age > 17)
эквивалентно
age <= 17
Таким образом, изменим условие
!(age <= 17 || height < 180)
Поменяем таким же образом вторую скобку
(gender == 'M' || gender == 'm')
на
!(gender != 'M' && gender != 'm')
получим
!(age <= 17 || height < 180) && !(gender != 'M' && gender != 'm')
Теперь можно применить это же правило и для всего выражения
!((age <= 17 || height < 180) || (gender != 'M' && gender != 'm'))
Порядок выполнения логических операторов
Рассмотрим выражение
a && b && c && d
где a, b, c, d – логические значения. Всё выражение равно истине тогда и только тогда, когда все операнды истинны. Если хотя бы один из операндов ложь, то остальные уже не важны. Поэтому, для оптимизации работы, вычисление происходит слева направо и останавливается, как только был найден первый операнд, равный нулю.
В си оператор присваивания может возвращать значение. Иногда он используется непосредственно в условии:
#define _CRT_SECURE_NO_WARNINGS #include <conio.h> #include <stdio.h> #include <stdlib.h> void main() { int a = 0; int *p = &a; if (a && (p = (int*) malloc(sizeof(int) * 2))) { printf("memory was allocated"); } free(p); _getch(); }
В данном случае, оператор malloc не будет выполнен, так как первый операнд a равен 0 (соответственно, всё выражение равно нулю). Таким образом, оператор free попытается очистить память, которую не может очистить (т.к. p продолжит ссылаться на a). Если же мы поменяем a = 1, то всё отработает без проблем.
То же самое происходит и при выполнение ||. Выражение
a || b || c || d
выполняется слева направо до тех пор, пока не встретит первое ненулевое значение. После этого выполнение останавливается, так как известно, что всё выражение равно истине.
Очевидно, что это касается не только оператора присваивания, но и любого другого вызова функции. Например, в этом случае функции foo будет вызвана, bar нет.
#define _CRT_SECURE_NO_WARNINGS #include <conio.h> #include <stdio.h> #include <stdlib.h> int foo() { printf("foo\n"); return 0; } int bar() { printf("bar\n"); return 0; } void main() { int a = 0, b = 1; if (a || foo() || b || bar()) { printf("OK\n"); } _getch(); }
Вывод – не используйте присваивания и вызовов функций (особенно, если они изменяют состояние программы) внутри условий.
