basic-c/sections/05-conditions.tex

265 lines
22 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\section{Условия, блоки кода, видимость}
\subsection{Условный оператор}
\paragraph{\code{if()}} пожалуй, самый часто используемый в любом языке программирования, в том числе и в языке С оператор. Оператор \code{if()} позволяет программе принять решение о выполнении или невыполнении того или иного действия в зависимости от текущего состояния. В случае, если условие в круглых скобках выполнится, выполнится и последующий код, который чаще всего пишется в фигурных скобках. Если условие в круглых скобках не выполнится, то все операторы внутри идущих следом фигурных скобок будут проигнорированы.
Например, зададим пользователю вопрос, хочет ли он, чтобы его поприветствовали. Для этого опишем переменную \code{char answer}, которая будет хранить ответ пользователя в виде символа, и спросим у пользователя в терминале, хочет ли он, чтобы мы его поприветствовали, выведем на экран строку с приглашением. Далее, при помощи уже знакомой нам функции \code{scanf();} считаем ответ пользователя в переменную, и, в зависимости от пользовательского ввода, программа либо поприветствует пользователя, либо нет, это решение будет принято с помощью оператора \code{if()}.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
char answer;
printf("do you want me to salute you (y/n)? ");
scanf ("%s", &answer);
if (answer == 'y') {
printf("Hello, user");
}
\end{lstlisting}
\end{figure}
\paragraph{\code{else}} Зачастую складываются ситуации, когда нужно выполнить разные наборы действий, в зависимости от результата проверки условия. Для таких случаев используется дополнение к оператору \code{if()} - оператор \code{else}, в котором описывается последовательность действий, выполняемая в случае, если условие в круглых скобках дало ложный результат. Код немного изменится, не будем приводить повторяющиеся части взаимодействия с пользователем, сконцентрируемся на условном операторе:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
if (answer == 'y') {
printf("Hello, user");
} else {
printf("I didn't want to salute you anyway");
}
\end{lstlisting}
\end{figure}
Как вы видите, в зависимости от того, что ввел пользователь, мы реализуем ту или иную ветку оператора \code{if}-\code{else}. Конструкция \code{if}-\code{else} является единым оператором выбора, то есть, выполнив код в фигурных скобках после \code{if}, программа не станет выполнять код в \code{else}, и наоборот.
\paragraph{\code{else if()}} Множественный выбор при помощи оператора \code{if} можно осуществить используя конструкцию \code{if}-\code{else if}-\code{else}. Данное усложнение также будет являться единым оператором выбора. Добавим в нашу конструкцию еще одно условие и опишем поведение для ответа <<да>> и ответа <<нет>>. В этом примере оператором \code{else} будет непонимание программы того, что ввел пользователь. Выведем в консоль надпись <<Я не могу понять Ваш ввод>>.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
if (answer == 'y') {
printf("Hello, user");
} else if (answer == 'n') {
printf("I didn't want to salute you anyway");
} else {
printf("I can't understand your input");
}
\end{lstlisting}
\end{figure}
Операторов \code{else if} в одном операторе выбора может быть сколько угодно, в отличие от оператора \code{if} и оператора \code{else}, которых не может быть больше одного.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
do you want me to salute you (y/n)? y
Hello, user
$ ./program
do you want me to salute you (y/n)? n
I didn't want to salute you anyway
$ ./program
do you want me to salute you (y/n)? x
I can't understand your input
$
\end{verbatim}
\end{figure}
\paragraph{Тернарный оператор.} Для короткой или внутристрочной записи условного оператора, а также для присваивания переменных по условию можно использовать \textbf{тернарный оператор}, также называемый оператором условного перехода и записываемый с помощью следующего синтаксиса: \code{(условие) ? истина : ложь}. Например, создадим три целочисленные переменные \code{а}, \code{b}, \code{c} и зададим двум из них какие-нибудь начальные значения, допустим \code{а = 10} и \code{b = 15}. Поставим себе задачу: присвоить переменной \code{c} наименьшее из значений \code{а} и \code{b}. Если мы будем использовать только что изученный нами оператор \code{if}-\code{else}, у нас должен получиться такой код:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int a = 10;
int b = 15;
int c;
if (a > b) {
c = b;
} else {
c = a;
}
\end{lstlisting}
\end{figure}
Запись условного оператора можно значительно сократить, поскольку в теле оператора происходит выбор: какое значение присвоить в \code{c} по условию в круглых скобках. Условие оставляем, а \code{а} и \code{b} переносим в секции истины и лжи, соответственно. Получим запись вида:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int a = 10;
int b = 15;
int c = (a > b) ? b : a;
\end{lstlisting}
\end{figure}
которая будет обозначать, что в случае если \code{a > b}, в переменную \code{c} запишется значение {b}, и наоборот, если \code{b > a}, то в переменную \code{c} запишется значение \code{а}. Также тернарный оператор можно использовать для удобного форматированного вывода, например опишем функцию \code{printf();}, которая будет печатать нам строку, и в зависимости от условия, это будет \code{"true"} либо \code{"false"}:
\begin{lstlisting}[language=C,style=CCodeStyle]
printf("%s", (1 > 0) ? "true" : "false");
\end{lstlisting}
Проверим, как это работает, и в результате видим true, потому что единица действительно больше нуля.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
true
$
\end{verbatim}
\end{figure}
\paragraph{Вложенные условия и сокращения}
Внутри фигурных скобок конструкций \code{if()} находится код программы, поэтому там могут находиться и другие условные операторы. Условия, расположенные таким образом, называются вложенными. Никаких ограничений на использование вложенных условий в языке С нет. В примере ниже показано, что условия всегда выполняются (единица в круглых скобках будет означать, что условие всегда истинно), а комментариями с многоточием показано, где может располагаться код программы.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
if (1) {
// operators
if (1) {
// operators
}
// operators
}
\end{lstlisting}
\end{figure}
Также важно отметить, что если после условных операторов следует только один оператор языка, как в примере ниже, то использование фигурных скобок не обязательно, хотя и считается хорошим тоном писать их всегда:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
// one operator condirion
if (1) {
// single operator
}
// also one operator condition
if (1)
// single operator
// another one operator condition
if (1)
// single operator
else
// single operator
\end{lstlisting}
\end{figure}
При использовании такой записи можно легко допустить ошибку: забыть о необходимости объединения кода фигурными скобками и предполагать, что несколько операторов могут выполниться по условию без фигурных скобок.
\frm{Часто это заблуждение приводит к трудноуловимым ошибкам в коде, когда программа компилируется, запускается, но работает не так, как следует.}
\subsection{Операции сравнения}
\paragraph{Арифметическое сравнение} это знакомые и привычные нам со школы операторы <<больше>> (\code{>}), <<меньше>> (\code{<}), <<больше или равно>> (\code{>=}), <<меньше или равно>> (\code{<=}), а также в языке С присутствуют в виде отдельных операторов <<проверка на равенство>>, которая записывается в виде двух знаков равенства (\code{==}), и <<проверка на неравенство>>, которая записывается в виде восклицательного знака и символа равенства (\code{!=}). Возвращают истину, когда выполняются соответствующие названиям условия, и ложь, когда условия не выполняются, что очевидно. Единственное исключение, которое было оговорено ранее (\hyperref[text:floats]{\ref{text:floats}}), сравнение разнотипных нецелочисленных значений должно осуществляться через сравнение допустимой дельты этих значений, например:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
float f = 0.5f;
double d = 0.5;
double diff = (f > d) ? f - d : d - f;
if (diff < 0.00001) {
// useful code here
}
\end{lstlisting}
\end{figure}
\paragraph{Логические операторы}
Их три: это \code{И (\&\&)}, \code{ИЛИ (||)}, \code{НЕ (!)}. В отличие от арифметических двоичных операторов - логические возвращают истину или ложь т.е. в случае языка С - \code{1} либо \code{0} и работают с операндами слева и справа целиком, а не поразрядно. В этом легко убедиться, попытавшись вывести на экран результат сравнения с заведомо неверным форматированием и получив ошибку, говорящую о том, что компилятор ожидал от нас число, а мы хотим его отформатировать в строку.
\begin{lstlisting}[language=C,style=CCodeStyle]
printf("%s\n", 1 == 1);
\end{lstlisting}
Некоторые компиляторы выдают ошибку на этапе компиляции, некоторые компилируют такой код, но программа не сможет выполниться и выдаст ошибку \textbf{Segmentation fault}, то есть ошибку доступа к памяти (попытка обратиться к недоступной памяти или попытка обратиться к памяти неподобающим образом). В этой конкретной ситуации, мы попытаемся интерпретировать как строку часть памяти, которая находится по адресу 1, что находится далеко за пределами доступа программы. Это поведение (ошибка компиляции или ошибка времени выполнения) зависит от большого количества факторов, таких как тип операционной системы, тип и версия компилятора, версия используемого стандарта языка.
Отдельного внимания заслуживает применение оператора поразрядного (арифметического) \code{ИСКЛЮЧАЮЩЕГО ИЛИ} в качестве логического. В случае такого применения оператор \code{ИСКЛЮЧАЮЩЕГО ИЛИ} фактически дублирует сравнение на неравенство, это легко объяснить, проведя анализ происходящего с числами при таком сравнении:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int a = 11; //00001011
int b = 11; //00001011
// ^ 00000000
if (a ^ b) {
printf("numbers are not equal\n");
}
\end{lstlisting}
\end{figure}
Данный код внутри фигурных скобок оператора \code{if()} никогда не выполнится, поскольку в С оператор сравнения работает с числами, интерпретируя ноль как ложь, а любое ненулевое значение - как истину, мы наблюдаем, что побитовое \code{ИСКЛЮЧАЮЩЕЕ ИЛИ} - это тоже самое, что проверка на неравенство.
\begin{itemize}
\item оператор \code{И (\&\&)} возвращает истину, только когда оба операнда истинны;
\begin{tabular}{|c|c|c|}
\hline
операнд & операнд & результат \\
\hline
0 & 0 & 0 \\
\hline
0 & 1 & 0 \\
\hline
1 & 0 & 0 \\
\hline
1 & 1 & 1 \\
\hline
\end{tabular}
\item оператор \code{ИЛИ (||)} возвращает истину, когда хотя бы один из операндов истинный;
\begin{tabular}{|c|c|c|}
\hline
операнд & операнд & результат \\
\hline
0 & 0 & 0 \\
\hline
0 & 1 & 1 \\
\hline
1 & 0 & 1 \\
\hline
1 & 1 & 1 \\
\hline
\end{tabular}
\item оператор \code{НЕ (!)} возвращает истину, когда операнд ложный;
\begin{tabular}{|c|c|}
\hline
операнд & результат \\
\hline
0 & 1 \\
\hline
1 & 0 \\
\hline
\end{tabular}
\end{itemize}
Используя логические операторы в программе, мы можем написать логику практически любой сложности. В языке С нет ограничений на использование сложных условий. Сложные условия это такие, где в круглых скобках выполняется более одного сравнения. Сравнения производятся в порядке заранее оговорённого приоритета. В списке ниже указаны операторы в порядке уменьшения их приоритета:
\begin{enumerate}
\item $!$
\item $<, <=, >, >=$
\item $==, !=$
\item $\&\&$
\item $||$
\end{enumerate}
Приведём короткий пример: дана некоторая целочисленная переменная, и нужно выяснить, не выходит ли эта переменная за рамки заданных значений, например от нуля до десяти. Условие можно будет скомбинировать так: \code{x >= 0 \&\& x <= 10}. В случае его истинности - выдадим сообщение о том, что \code{х} подходит.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int x = 7;
if ((x >= 0) && (x <= 10)) {
printf("X Fits!\n");
}
\end{lstlisting}
\end{figure}
В данной записи мы видим, что сначала \code{х} сравнивается с нулём, затем с десятью, и в конце результаты будут сравнены между собой.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
X Fits!
$
\end{verbatim}
\end{figure}
Самый не приоритетный оператор - тернарный, внимательный читатель мог заметить, что он даже не вошёл в список выше, это сделано, поскольку использование тернарного оператора внутри условий нежелательно. Тернарный оператор внутри условий резко снижает читаемость кода и усложняет его интерпретацию. Если Вы сомневаетесь в приоритете сравнений или Вам необходимо описать какое-то очень сложное условие, всегда можно воспользоваться простыми математическими круглыми скобками, задав приоритет операций явно. В таком случае в первую очередь будут выполнены операции в скобках.
\subsection{Блоки кода и область видимости}
Говоря об операторах языка С и управляющих конструкциях, нельзя не сказать о <<блоках кода>> и <<областях видимости>>. Как видно, условные операторы содержат записи в фигурных скобках. В такие же скобки заключён код функции \code{main}. Эти скобки называются <<операторными>>, а то, что в них содержится, называется <<блоком кода>> или <<телом>> оператора или функции. Все переменные, которые инициализируются внутри блока кода, существуют и <<видны>> только внутри кодового блока. Поэтому пространство между операторными скобками также называют <<областью видимости>>.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int x = 7;
if ((x >= 0) && (x <= 10)) {
int var = 0;
printf("X Fits!\n");
}
printf("%d", var);
\end{lstlisting}
\end{figure}
На этом примере можно увидеть, что мы не можем напечатать значение переменной \code{var}, поскольку она была создана внутри блока кода оператора \code{if()} и перестала существовать для нашей программы как только мы вышли за его пределы. Такой код даже не скомпилируется:
\begin{figure}[h!]
\begin{verbatim}
$ gcc -o program main.c
error: 'var' undeclared (first use in this function);
did you mean 'char'?
printf("%d", var);
^~~
char
$
\end{verbatim}
\end{figure}