basic-c/sections/05-conditions.tex

269 lines
22 KiB
TeX
Raw Normal View History

\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{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
printf("%s", (1 > 0) ? "true" : "false");
\end{lstlisting}
\end{figure}
Проверим как это работает, и в результате видим 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;
fouble 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{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
printf("%s\n", 1 == 1);
\end{lstlisting}
\end{figure}
Некоторые компиляторы выдают ошибку на этапе компиляции, некоторые компилируют такой код, но программа не сможет выполниться и выдаст ошибку \textbf{Segmentation fault}. Это зависит от большого количества факторов, таких как тип операционной системы, тип и версия компилятора, версия используемого стандарта языка.
Отдельного внимания заслуживает применение оператора поразрядного (арифметического) \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}