На этом занятии вы увидите, как в программах можно делать логические выводы на уровне:
- true
– истина;
- false
– ложь.
Так как
компьютер – это вычислительная машина, то понятия «истина» и «ложь» должны
выражаться на уровне чисел. В большинстве, а может быть во всех языках
программирования, значение false определяется как 0, а true – как любое
ненулевое значение. Поэтому константы true и false часто определяют
как:
true = 1; false = 0
До стандарта C99 язык Си не
предполагал наличия какого-либо специального булевого типа данных. Вместо этого
использовался любой целочисленный тип, и если значение переменной равнялось
нулю, это означало false (ложь), а любое другое значение,
отличное от нуля – true (истина). Например, так:
char fl_view = 0; // false
int fl_open_file = 1; // true
Но стандарт C99 предоставляет
нам новый тип (новое ключевое слово):
_Bool
И булевы
переменные стало возможно определять следующим образом:
_Bool fl_view = 0; // false
На самом деле,
тип _Bool определен на
базе обычного целочисленного типа, который соответствует наименьшему
допустимому размеру, то есть, char. В этом легко убедиться,
воспользовавшись оператором sizeof:
#include <stdio.h>
int main(void)
{
_Bool fl_view = 0; // false
printf("Size of _Bool: %d\n", sizeof(_Bool));
return 0;
}
После запуска
программы увидим строчку:
Size of _Bool: 1
К сожалению,
ключевые слова true или false по-прежнему не определены. Поэтому
булевой переменной fl_view нужно явно присваивать значение 0 в
качестве false и 1 – для true. Причем,
никакие другие значения она принимать не может. Если попытаться присвоить,
например, число 10:
то fl_view его преобразует
к единице. И, вообще, любое ненулевое значение приравнивается единице. В этом
ключевое отличие типа _Bool, например, от типа char, который
позволяет представлять любые целые числа в диапазоне [0; 255].
А теперь
внимательнее посмотрим на написание этого типа. Вначале идет символ подчеркивания,
а затем, с заглавной буквы слово «Bool». Довольно
неудобная и «корявая» запись. Дело в том, что это была первая официальная
попытка определить булевый тип в языке Си. И никто тогда точно не мог сказать,
как эта идея будет воспринята сообществом программистов. Кроме того, для
совместимости с прежними компиляторами, этот тип можно было легко заменить на
любой другой базовый целочисленный, например, char. И программы бы
компилировались без проблем.
В
действительности, именно в такой записи тип _Bool практически не
использовался на практике. Позже его подменили более изящным словом bool. И, кроме того,
такой тип официально появился в языке С++, который является естественным
развитием языка Си. Так вот, чтобы в программах на языке Си в соответствии со
стандартом C99 можно было бы
использовать более приятную и общеупотребительную запись булевого типа bool, следует
подключить заголовочный файл stdbool.h:
В нем не только
переопределен тип _Bool как bool, но и введены две константы:
true = 1; false = 0
Поэтому
переменную fl_view в нашей
программе теперь можно определить так:
Эта строчка
выглядит естественнее и понятнее для программиста, чем корявый тип _Bool и числа 0 или
1.
Операции сравнения
Я, думаю, с
определением переменных булевого типа в языке Си все понятно. Давайте теперь
посмотрим, как можно их использовать в операциях сравнения. Вначале приведу
список этих операций.
Операция
|
Описание
|
==
|
(Два
равно). Сравнение на равенство.
|
!=
|
Сравнение
на неравенство.
|
<
|
Сравнение
на меньше.
|
>
|
Сравнение
на
больше.
|
<=
|
Сравнение
на меньше или равно.
|
>=
|
Сравнение
на больше или равно.
|
Все эти операции
являются бинарными, то есть, слева и справа от них прописываются выражения
(операнды):
<левый
операнд> <операция сравнения> <правый операнд>
В качестве
операндов может выступать любая конструкция языка Си, со значениями которых
возможны сравнения. Часто это переменные и числовые литералы.
Обратите
внимание, все операции сравнения являются именно операциями, а не операторами,
то есть, они позволяют выполнять некоторое сравнение и возвращают вычисленный
результат в виде значений:
0 – false;
1 – true.
Например:
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
double x = 5.67;
bool fl_view = x < 0;
printf("%d\n", fl_view);
return 0;
}
Так как в нашем
примере x больше нуля, то
операция сравнения на отрицательное значение вернет 0, которое соответствует
понятию «ложь» (false). А вот если вместо нуля указать,
например, значение 10:
то переменная fl_view будет равна 1,
что соответствует понятию «истина» (true). И так
работают все операции сравнения:
double x = 5.67;
int var_i = 7;
bool fl_view = x < 10; // true
bool res_1 = 5 > 7; // false
bool res_2 = x+2 >= 10.56; // false
bool res_3 = var_i == 7; // true
bool res_4 = var_i != 7; // false
Причем,
приоритет операций сравнения выше приоритета операции присваивания. Поэтому
сначала выполняются сравнения и только потом – присваивания. А арифметические
операции выше операций сравнения, поэтому x+2 будет
выполнено до сравнения на больше или равно. Кроме того, обратите внимание на
операцию сравнения на равенство. Она записывается как два символа равно (==). Как
мы знаем, одно равно – это операция присваивания, поэтому для сравнения на
равенство ввели обозначение из двух символов равно. И здесь начинающие
программисты очень часто делают ошибку. Для сравнения на равенство значений они
пишут не два, а, по привычке, одно равно, например, так:
При этом
компилятор может даже не выдать никаких предупреждений и в любом случае
переведет программу в машинный код. Только работать она будет не так, как
задумывалась программистом. И чисто визуально найти такую ошибку бывает не
просто, особенно, если в голове не зафиксировано, что сравнение – это два знака
равно. В общем, если вы только начинаете постигать азы программирования на
языке Си, то выпишите этот момент на бумажке и прилепите ее к монитору. Я
гарантирую, что найдется немало тех, кто ее совершит.
Также следует
помнить, что операции:
<=
и >=
нужно
прописывать именно в таком виде. Иногда, правда редко, меняют местами символы и
пытаются прописать:
=< и => (неверная
запись)
Это приведет к
синтаксической ошибке.
Давайте в
качестве короткого примера приведу программу ввода числового значения и
определение четности числа:
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
int digit;
scanf("%d", &digit);
bool even = digit % 2 == 0;
printf("%d\n", even);
return 0;
}
После запуска и
ввода целого значения мы увидим 0 – для нечетных чисел и 1 – для четных.
Составные операции сравнения
Давайте теперь
поставим более сложную задачу и определим, попадает ли число (значение
переменной) в диапазон [-2; 5]? Какие логические умозаключения здесь нужно
провести, чтобы ответить на этот вопрос? Для простоты представим, что у нас
имеется переменная:
Очевидно, чтобы
она попадала в диапазон [-2; 5], нужно соблюдение двух условий:
y >= -2
и
y <= 5
В языке Си это
можно реализовать следующим образом:
bool is_range = y >= -2 && y <= 5;
Здесь операция &&
означает логическое И. При этом общее условие истинно, если истинно каждое из
подусловий: y >= -2 и y <= 5. Благодаря
этому мы будем получать значение true (единица), если y принадлежит диапазону
[-2; 5] и false (ноль) в
противном случае.
А теперь сделаем
противоположную проверку, что переменная y не попадает в
диапазон [-2; 5]. Очевидно, это будет происходить, если:
y < -2 или y > 5
В языке Си такая
составная операция сравнения может быть записана в виде:
bool is_not_range = y < -2 || y > 5;
Операция ||
означает логическое ИЛИ и возвращает истину (true), если истинно
хотя бы одно из подусловий. В нашем примере, это проверка, что y или меньше -2
или больше 5. Часто начинающие программисты здесь делают логическую ошибку и
подобное сравнение записывают с использованием операции И следующим образом:
bool is_not_range = y < -2 && y > 5;
В этом случае я
прошу назвать число, которое одновременно меньше -2 и больше 5. Как правило, после
этого в головах все встает на свои места. Не путайте принцип действия этих двух
операций: логическое ИЛИ и логическое И.
На самом деле
противоположную проверку непопадания в диапазон [-2; 5] можно было бы
реализовать путем инвертирования ранее вычисленного значения is_range следующим
образом:
bool is_not_range = !is_range;
Здесь
восклицательный знак – это унарная операция НЕ, которая может быть применена к
любому выражению. Принцип ее работы заключается в инвертировании булевого
значения:
true -> false; false->true.
Приоритеты операций И, ИЛИ, НЕ
Приоритеты всех
этих трех логических операций следующие:
Логическое
ИЛИ (||)
|
1
|
Логическое
И (&&)
|
2
|
Логическое
НЕ (!)
|
3
|
То есть,
наибольший приоритет имеет унарная операция НЕ, затем, операция И, и самый
низкий – у операции ИЛИ.
Все эти
приоритеты необходимо строго соблюдать для составления корректных условий.
Например:
int x = 5;
bool is_correct = x % 2 == 0 || x % 3 == 0 && x > 5;
Это составное
условие эквивалентно следующему:
bool is_correct = x % 2 == 0 || (x % 3 == 0 && x > 5);
то есть, сначала
проверяется, что число x кратно 2
(четное) ИЛИ число кратно 3 и при этом больше 5. Обратите внимание здесь на два
важных момента. Во-первых, стандартом языка Си определен строгий порядок
проверок слева-направо при вычислении составных логических операций. Это
значит, мы можем быть абсолютно уверены, что сначала выполнится проверка x % 2 == 0 и
только после этого следующее подусловие x % 3 == 0
&& x > 5. Причем,
в нем также сначала проверяется первое x % 3 == 0 и
только потом второе x > 5. Во-вторых, если в процессе проверки
значение всей составной логической операции становится известным, то вычисления
прерываются и не идут дальше. Например, в условии:
bool fl_digit = x != 0 && 10 / x > 1;
сначала будет
проверено, что переменная x не равна нулю, и
если это не так (то есть это первое подусловие равно false), то дальше
проверки делать не имеет смысла, т.к. при любом булевом значении второго
подусловия (10/x > 1) общее все равно будет принимать значение false. Благодаря
такому поведению, мы можем без проблем вычислять деление 10 / x после проверки,
что x != 0. Этим на
практике довольно часто пользуются.
Но все это нужно
применять очень аккуратно. Например, если прописать следующее составное
условие:
bool is_read = x < 0 && scanf("%d", &x) == 1;
то функция scanf() не будет
вызвана, если x больше или равен
нулю.
И последнее,
важный, хотя и очевидный момент. Если нам нужно поменять приоритеты операций,
то для этого можно использовать все те же круглые скобки. Например:
bool is_correct = (x % 2 == 0 || x % 3 == 0) && x > 5;
Теперь это
условие будет истинно, если x кратно 2 или 3 и
больше 5.