basic-c/sections/04-variables.tex

374 lines
40 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{Переменные в программе на языке С} Это некие \textit{именованные контейнеры}, тип которых строго описан при их создании, каждый из которых может содержать одно и только одно значение в единицу времени. Названия или имена переменных не могут начинаться с цифр и спецсимволов, а также не должны повторяться (\hyperref[text:naming]{\ref{text:naming}}).
\frm{\textbf{Идентификатор} переменной - это её имя, которое для программы не существует без привязки к типизации, то есть для объявления переменной мы пишем её тип и название, что вместе составляет идентификатор. По идентификатору переменной мы можем записать в неё значение, прочитать текущее значение, узнать адрес хранения этого значения, и т.д. \textbf{Литерал} - это число или строка, которые мы пишем в тексте явно. Литералу нельзя присвоить значение, литерал это и есть значение. Литерал (ни строковый, ни числовой) нельзя изменить. Если изменить какой-то литерал, то это будет уже другой литерал, явно изменённое в коде значение. Также есть термины \code{lvalue} и \code{rvalue}. Если очень сильно упрощать, то их можно отождествить с идентификатором и литералом: \code{lvalue} - это то, \textit{куда} присваивается, \code{rvalue} - это то, \textit{что} присваивается}
Переменные делятся на целочисленные, символьные, указатели и числа с плавающей точкой (англ. floating point, дробное число). Все, кроме указателей и символьных переменных бывают как знаковыми так и беззнаковыми. То есть в знаковых самый старший бит в двоичной записи этих переменных отводится под определение, является ли число отрицательным, или положительным, в беззнаковых все биты используются для записи числа, что увеличивает его диапазон возможных значений, но позволяет записать только положительные числа. В классическом С нет булевого типа, вместо него используется целое число и значения нуля для \textbf{лжи} и \textit{любое} другое число для \textbf{истины}, обычно это единица. Об указателях и булевой алгебре мы будем подробно говорить в одном из последующих разделов.
\begin{figure}[h!]
\centering
\begin{tabular}{|p{1.5cm}|p{6.6cm}|p{2.4cm}|}
\hline
Тип & Пояснение & Спецификатор формата \\
\hline
char & Целочисленный, самый маленький из адресуемых типов, диапазон: [\textminus128, +127] & \%c \\
\hline
short\newline short int & Тип короткого целого числа со знаком, диапазон: [\textminus32 768, +32 767] & \%hi \\
\hline
int & Основной тип целого числа со знаком, диапазон: [\textminus2 147 483 648, +2 147 483 647] & \%i или \%d \\
\hline
long\newline long int & Тип длинного целого числа со знаком, диапазон: [\textminus2 147 483 648, +2 147 483 647] & \%li или \%ld \\
\hline
long long\newline long long int & Тип двойного длинного целого числа со знаком, диапазон: [\textminus9 223 372 036 854 775 808, +9 223 372 036 854 775 807] & \%lli \\
\hline
float & Тип вещественного числа с плавающей запятой (одинарной точности) & \%f (автоматически преобразуется в double для printf()) \\
\hline
double & Тип вещественного числа с плавающей запятой (двойной точности) & \%f(\%F) (\%lf(\%lF) для scanf())\newline\%g \%G \%e \%E \\
\hline
long double & Тип вещественного числа с плавающей запятой, ставящийся в соответствие формату повышенной точности с плавающей запятой & \%Lf \%LF \%Lg\newline\%LG \%Le \%LE \\
\hline
\end{tabular}
\caption{Основные типоы данных в языке С}
\label{tab:types}
\end{figure}
\newpage
\paragraph{Символьный тип} не такой простой, как может показаться на первый взгляд. Если вкратце, то в переменной типа \code{char} хранится число, которое можно интерпретировать как символ. По умолчанию тип знаковый, то есть может содержать значения от \textminus128 до +127, но символы в таблице ASCII\footnote{American standard code for interaction interchange}, что совершенно логично, имеют только положительные индексы, поэтому в читаемый текст в стандартном С можно превратить только латинский алфавит и некоторый набор знаков и символов, находящиеся на первых 128-ми местах в этой таблице. Также можно явно указать компилятору, что мы хотим использовать эту переменную как беззнаковую, для этого используется ключевое слово \code{unsigned}, что позволит нам хранить только положительные числа гораздо больших значений. Например для переменной типа \code{unsigned char} это будут значения от 0 до 255, а для переменной типа \code{unsigned int} можно можно хранить значения от 0 до +4.294 миллиардов с какими-то копейками. В более поздних редакциях языка были утверждены типы \code{long long} и другие, для хранения 64-х разрядных целых чисел.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
unsigned char symbol = 75;
printf("75 stands for: %c\n", symbol);
\end{lstlisting}
\end{figure}
Соответственно, программа выше выведет в терминал информацию о том, какой именно символ в таблице ASCII соответствует позиции 75, по большому счёту, можно было обойтись и без ключевого слова \code{unsigned}, но когда мы говорим именно о символах, а не просто о минимальных по занимаемому размеру целых числах, принято выделять их:
\begin{figure}[h!]
\begin{verbatim}
$ ./program
75 stands for: K
$
\end{verbatim}
\end{figure}
\paragraph{Числа с плавающей точкой (дробные)} представлены двумя типами: четырёхбайтный \code{float} и восьмибайтный \code{double} (также называемый long float). Хранятся в памяти в неявном виде, а разделённые на мантиссу экспоненту и знак, что делает их одними из самых сложных в работе\footnote{Здесь имеется ввиду внутренняя работа самого компьютера, а не работа с такими типами с точки зрения программиста. Именно из-за сложности, многие старые процессорные архитектуры не имели возможности работать с переменными такого типа}.
\label{text:floats}
\frm{Важно, что компилятор, при работе с текстом программы считает все литералы в коде числами типа \code{double}, а значит код вида \code{float var = 0.123;} будет отмечен компилятором как неверный, поскольку компилятор не в состоянии положить восемь байт информации в четырёхбайтную переменную. То есть, если мы хотим инициализировать переменную типа \code{float}, то нам нужно специальным образом пометить литерал: \code{float var = 0.123f;}. Символ \code{f} в конце явно указывает на то, что литерал имеет тип \code{float}.}
При работе с числами с плавающей точкой нужно обращать особенное внимание на тип переменной, поскольку сравнение внешне одинаковых чисел разных типов с почти стопроцентной вероятностью даст ложный результат, в отличие от сравнения простых целых чисел. Об операциях и операторах сравнения мы поговорим позже, сейчас просто приведём наглядный пример того, как это работает.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
float real = 5,345f; // 4 bytes
double realdouble = 5,345; // 8 bytes
printf("float and double: %d\n", real == realdouble);
int a = 10;
int b = 10;
printf("integers: %d\n", a == b);
\end{lstlisting}
\end{figure}
Запустим код, выводящий результаты сравнений и убедимся в том, что прямое сравнение дробных чисел допускать нежелательно. Обычно, если есть необходимость в сравнении дробных чисел применяют сравнение с некоторой допустимой точностью, например, до третьего или пятого знака после запятой. О таких сравнениях мы поговорим позднее.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
float and double: 0
integers: 1
$
\end{verbatim}
\end{figure}
\label{text:pointers}
\paragraph{Тип данных - указатель.} Как было сказано - переменная это именованный контейнер. У каждого такого контейнера есть свой собственный адрес в оперативной памяти. Язык С позволяет узнать этот адрес и работать с ним. Оператор взятия адреса это знак амперсанд (\&), написанный перед именем переменной. То есть у любой переменной всегда есть значение и адрес где это значение хранится. Для вывода в консоль адреса используется специальный заполнитель - \code{\%p}.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
printf("Variable a has value: %d \n", a);
printf("Variable a stored at: %p \n", &a);
\end{lstlisting}
\end{figure}
При неоднократном запуске кода можно обратить внимание, что первые цифры в адресе всегда остаются неизменными, а последние меняются редко, это связано с тем, что в современных операционных системах пользователю для его работы чаще всего выделяется некоторое адресное пространство, которое потом просто переиспользуется и перезаписывается. При запуске точно такого же кода на Вашем компьютере, Вы увидите, что адрес хранения переменной наверняка будет отличаться, это нормально.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
Variable a has value: 10
Variable a stored at: 0x7ffe6aa136cf
$
\end{verbatim}
\end{figure}
\subsection{Базовая арифметика}
Раз уж в предыдущем разделе мы коснулись арифметических выражений, поговорим немного об арифметике.
\paragraph{Простые арифметические операции.} В языке С поддерживаются все базовые арифметические операции, такие как сложение, вычитание, умножение, деление. Операции бинарной арифметики (булевой алгебры), такие как \code{И}, \code{ИЛИ}, \code{НЕ}, \code{ИСКЛЮЧАЮЩЕЕ ИЛИ}, \code{СДВИГИ}. А также все вышеперечисленные операции с последующим присваиванием в первую переменную. Для начала, инициализируем переменную типа \code{int} значением, например, \code{70}, и выведем ее в консоль. Мы можем производить с этой переменной все привычные базовые арифметические манипуляции, ровно также, как мы можем производить эти манипуляции с литералом, который ей присваивается:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int variable = 70;
printf("The variable = %d\n", variable);
printf("The variable = %d\n", variable + 10);
variable = variable + 50;
printf("The variable = %d\n", variable);
variable = 123 + 50 * 12;
printf("The variable = %d\n", variable);
\end{lstlisting}
\end{figure}
То есть, нам доступны операции сложения, умножения, вычитания и деления. Как видно в результате работы программы, есть прямая зависимость между действительным значением переменной и порядком присваивания в неё значения, так в третьем выводе значение равно 120, то есть $70 + 50$, а значит во втором выводе присваивания нового значения $70 + 10$ в переменную \code{variable} не произошло.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
The variable = 70
The variable = 80
The variable = 120
The variable = 723
$
\end{verbatim}
\end{figure}
\paragraph{Оператор деления} заслуживает особенного внимания, поскольку у него есть два режима работы, отличающие этот оператор от привычной нам арифметики: если мы производим операции с целыми числами такими как \code{int}, \code{short} или \code{char} оператор деления всегда будет возвращать только целые числа, \textbf{отбросив дробную часть}. Это происходит из-за оптимизаций компилятора, то есть если операнды - это целые числа, то и результатом по мнению компьютера может быть только целое число, а из-за строгости типизации мы не можем положить в целочисленную переменную значение с плавающей точкой.
Таким образом, отслеживание точности вычислений полностью возлагается на плечи программиста. Важно, что компилятор нам в этом помогает, хоть и не делает всю работу за нас. Компилятор, при преобразовании операций автоматически приводит типы операндов к наиболее подходящему и широкому. То есть, если мы, например, складываем два числа \code{int} и \code{char}, то \code{char} будет автоматически расширен до \code{int}, потому что максимальное значение \code{char} точно поместится в переменную типа \code{int}, а максимальное значение \code{int} точно никак не сможет поместиться в переменную с типом \code{char}. Точно также если умножать \code{int} и \code{float}, то \code{int} будет преобразован во \code{float} по той же причине - \code{int} совсем никак не умеет работать с плавающей точкой, а \code{float} вполне может содержать число без дробной части. Из-за этого языки С/С++ считаются слабо типизированными.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
variable = 10;
variable = variable / 3;
printf("The variable = %d\n", variable);
float var = 10;
var = var / 3;
printf("The var = %f\n", var);
\end{lstlisting}
\end{figure}
В примере выше, обратите внимание, что переменная \code{variable} не была инициализирована, а ей просто было присвоено значение. Это сделано потому, что данный участок кода является продолжением кода примеров простой арифметики из предыдущего параграфа. При использовании оператора деления с целочисленными операндами теряется точность вычислений, что недопустимо.
\frm{Чтобы оператор деления отработал в не целочисленном режиме, нужно, чтобы хотя бы один операнд был не целочисленным.}
Чаще всего целочисленные переменные используют в качестве счётчиков, индексов и других вспомогательных переменных, поэтому математические операции с ними весьма распространены.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
The variable = 3
The var = 3.333333
$
\end{verbatim}
\end{figure}
\paragraph{Деление по модулю.} Также особенного внимания заслуживает оператор получения остатка от деления, иногда называемый оператором взятия по модулю. Записывается как символ \code{\%} и возвращает остаток от целочисленного деления первого числа на второе:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int remain = variable % 5;
printf("Division remainder of %d by %d: %d\n", variable, 5, remain);
variable = variable + 50;
printf("The variable = %d\n", variable);
variable += 50;
printf("The variable = %d\n", variable);
\end{lstlisting}
\end{figure}
Любые арифметические операции можно выполнить с последующим присваиванием в первую переменную. То есть это означает, что запись вида \code{variable = variable + 50;} можно сократить до \code{variable += 50;} и запустив следующий код мы можем убедиться, что начальное значение переменной увеличилось сначала на 50, а затем ещё на 50.
\begin{figure}[h]
\begin{verbatim}
$ ./program
Division remainder of 3 by 5: 3
The variable = 53
The variable = 103
$
\end{verbatim}
\end{figure}
\paragraph{Инкремент и декремент.} Так, бегло рассмотрев арифметику в языке С нельзя не упомянуть об операторах увеличения и уменьшения значения переменной на единицу с последующим присваиванием. Они называются операторами инкремента (\code{++}) и декремента (\code{\textminus\textminus}). Это унарный оператор, поэтому записывается со своим операндом строго без пробелов: \code{variable++;} и редко используется как самостоятельный оператор на отдельной строке. У операторов инкремента и декремента есть два вида записи: префиксный и постфиксный. Их отличает время применения текущего значения и его изменения. При постфиксной записи, сначала происходит применение текущего результата, а затем его изменение, а при префиксной записи - сначала изменение, а затем применение. Например:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
variable = 50;
printf("1. Postfix increment: %d\n", variable++);
printf("1. Next line of code: %d\n", variable);
variable = 50;
printf("2. Prefix increment: %d\n", ++variable);
printf("2. Next line of code: %d\n", variable);
\end{lstlisting}
\end{figure}
В результате выполнения этого кода мы можем видеть на экране следующий результат:
\begin{figure}[h!]
\begin{verbatim}
$ ./program
1. Postfix increment: 50
1. Next line of code: 51
2. Prefix increment: 51
2. Next line of code: 51
$
\end{verbatim}
\end{figure}
\subsection{Булева алгебра и двоичные вычисления}
\paragraph{Двоичная система счисления} представляет особенный интерес для области информационных технологий, поскольку вся электроника работает по принципу <<есть напряжение или нет напряжения>>, единица или ноль. Все числа из любых систем счисления в результате преобразуются в двоичную и представляются в виде набора единиц и нулей. Так для записи десятичного числа \code{116} используется двоичная запись \code{1110100}. Преобразование из системы счисления с бОльшим основанием в систему счисления с меньшим основанием производится последовательным делением исходного числа на основание системы счисления и записи остатков такого деления в младшие разряды. Например:
\[
\frac{116}{2} = \frac{58 (0)}{2} = \frac{29 (0)}{2} = \frac{14 (1)}{2} = \frac{7 (0)}{2} = \frac{3 (1)}{2} = \frac{1 (1)}{2} = 1 < 2
\]
В этом примере полученные остатки от деления записаны в скобках и можно обратить внимание на то, что они полностью повторяют запись числа \code{116} показанную ранее в зеркальном отражении. Обратное преобразование - это последовательное умножение разрядов числа на величину каждого разряда с их аккумулированием к общему результату:
\begin{equation*}
\begin{gathered}
1110100 = 0*2^0 + 0*2^1 + 1*2^2 + 0*2^3 + 1*2^4 + 1*2^5 + 1*2^6 = \\ 0*1 + 0*2 + 1*4 + 0*8 + 1*16 + 1*32 + 1*64 = \\ 4 + 16 + 32 + 64 = 116
\end{gathered}
\end{equation*}
Поскольку двоичная система счисления является основной для компьютерной техники, помнить, например, значения степеней двойки - обычно, хорошее подспорье в работе.
\paragraph{Булева алгебра} это один из базовых, но вместе с тем один из самых мощных инструментов в программировании. Двоичные вычисления выполняются быстрее десятичных, поскольку являются естественными для цифровой техники. В бинарной алгебре используются операторы \code{И (\&)}, \code{ИЛИ (|)}, \code{НЕ (\~{})}, \code{ИСКЛЮЧАЮЩЕЕ ИЛИ (\^{})} и операции \code{СДВИГА} влево (\code{$<<$}) и вправо(\code{$>>$}). Работают эти операторы относительно разрядов двоичного представления чисел, где истина это единица, а ложь - это ноль.
\frm{Разница между логическими и арифметическими бинарными операторами в представлении операндов: логические оперируют числовыми литералами и переменными целиком, а арифметические числами поразрядно. Работу логических операторов мы рассмотрим в следующем разделе.}
Условия истинности двоичных арифметических операторов следующие:
\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}
\item оператор \code{ИСКЛЮЧАЮЩЕГО ИЛИ} возвращает единицу когда операнды различаются.
\begin{tabular}{|c|c|c|}
\hline
операнд & операнд & результат \\
\hline
0 & 0 & 0 \\
\hline
0 & 1 & 1 \\
\hline
1 & 0 & 1 \\
\hline
1 & 1 & 0 \\
\hline
\end{tabular}
\end{itemize}
На основе этих знаний мы можем для примера написать программу, меняющую местами значения переменных без использования третьей, вспомогательной и быть уверенными, что переполнения переменных не произойдёт, как это могло бы произойти, например, при использовании сложения и обратного вычитания. Объявим две переменных \code{a} и \code{b}, присвоим им значения и выведем их в консоль. Также подготовим вывод измененных значений \code{a} и \code{b} в консоль:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
char a = 11;
char b = 15;
printf("a = %d, b = %d\n", a, b);
// here will be the swapping algorithm
printf("a = %d, b = %d\n", a, b);
\end{lstlisting}
\end{figure}
Далее, напишем некую конструкцию, которая при детальном изучении не представляет из себя никакой магии. В переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в переменную \code{b} нужно будет записать результат вычисления \code{b \^{} a} и наконец в переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в коде ниже будет приведена сразу сокращённая запись:
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
a ^= b;
b ^= a;
a ^= b;
\end{lstlisting}
\end{figure}
Нужно сразу оговориться, что этот алгоритм может некорректно работать с одинаковыми и отрицательными числами, это будет зависеть от компилятора, поэтому, если включать этот алгоритм в состав более сложных, лучше осуществлять дополнительные проверки. Вывод этой конкретной программы будет следующим:
\begin{figure}[h!]
\begin{verbatim}
$ ./program
a = 11, b = 15
a = 15, b = 11
$
\end{verbatim}
\end{figure}
Дополнительно, для написания этого документа был проведён ряд тестов:
\begin{figure}[h!]
\begin{verbatim}
Test project ~/Documents/c-basic/build
1/7 Test #1: Swap.TwoPosNumbers ..... Passed 0.00 sec
2/7 Test #2: Swap.SamePosNumbers .... Passed 0.00 sec
3/7 Test #3: Swap.OneNegNumber ...... Passed 0.00 sec
4/7 Test #4: Swap.TwoNegNumbers ..... Passed 0.00 sec
5/7 Test #5: Swap.SameNegNumbers .... Passed 0.00 sec
6/7 Test #6: Swap.BareOverflow ...... Passed 0.00 sec
7/7 Test #7: Swap.OverflowNumbers ...***Failed 0.00 sec
86% tests passed, 1 tests failed out of 7
Total Test time (real) = 0.04 sec
\end{verbatim}
\end{figure}
Ожидаемо, не прошёл тест, в котором присутствовало переполнение переменной, этот случай также был отмечен предупреждением компилятора о том, что программист пытается присвоить переменной значение, большее, чем переменная способна вместить.
\frm{Здесь был преднамеренно использован тест с провальным результатом, для более явной демонстрации происходящего внутри алгоритма, и потому что нам не нужна дальнейшая компиляция продакшн кода. Обычно, тест-кейсы с ожидаемым провалом пишутся с инверсией проверки, то есть, если мы ожидаем, что некоторые значения не будут равны эталонным, необходимо проверять значения на неравенство, таким образом все тест-кейсы пройдут успешно.}
Рассмотрим происходящее для приведённого примера кода пошагово: оператор \code{ИСКЛЮЧАЮЩЕГО ИЛИ} выполняется следующим образом: результат будет равен \code{1} если операнды (в данном случае, разряды двоичного представления числа) различаются и \code{0} если они совпадают. Изначально имеем две переменных, \code{a} и \code{b} - число \code{11} типа \code{char} (в двоичном представлении это \code{00001011}), и число \code{15} (это \code{00001111}). В коде ниже можно наглядно рассмотреть работу оператора \code{ИСКЛЮЧАЮЩЕГО ИЛИ}:
\begin{figure}[h!]
\begin{verbatim}
// a = 11 (00001011)
// b = 15 (00001111)
a = a ^ b; //00000100
\end{verbatim}
\end{figure}
После выполнения первого оператора в переменную \code{a} будет положено промежуточное число \code{00000100} это цифра \code{4}, а в переменной \code{b} останется число \code{15 (00001111)}. Ко второму действию мы приходим с начальными значениями: \code{a = 4 (00000100)}, \code{b = 15 (00001111)}, производим операцию \code{ИСКЛЮЧАЮЩЕГО ИЛИ} с последующим присваиванием в переменную \code{b} и получаем \code{00001011} т.е. \code{b = 11 (00001011)}.
\begin{figure}[h!]
\begin{verbatim}
// b = 15 (00001111)
// a = 4 (00000100)
b = b ^ a; //00001011
\end{verbatim}
\end{figure}
И после выполнения третьего оператора \code{ИСКЛЮЧАЮЩЕГО ИЛИ} в переменную \code{a} будет положено значение \code{00001111} это цифра \code{15}.
\begin{figure}[h!]
\begin{verbatim}
// a = 4 (00000100)
// b = 11 (00001011)
a = a ^ b; //00001111
\end{verbatim}
\end{figure}
\paragraph{Операции сдвига} бывают логические, арифметические и циклические. В языке С реализован логический сдвиг, то есть недостающие разряды при сдвиге заполняются нулями, а выходящие за пределы хранения переменной теряются без возможности восстановления. Итак, допустим, что нам нужно переменную \code{a} сдвинуть влево на \code{3} бита, на самом деле это означает что мы переменную \code{a} умножим на $2^3$. В примере ниже, переменную \code{b} мы сдвинем вправо на \code{2} бита, это означает, что мы переменную \code{b} разделим на $2^2$, при этом важно упомянуть, что будет произведено целочисленное деление.
\frm{\textbf{Сдвиг влево} числа k на n - это \textit{умножение} $k*2^n$. \textbf{Сдвиг вправо} числа k на n - это \textit{целочисленное деление} $\frac{k}{2^n}$.}
Это тоже самое что записать $a * 8$ и $b / 4$. Просто на маломощных компьютерах выполнится это гораздо быстрее. Бинарная алгебра это большая и интересная тема, на которую написано немало статей и даже книг, но подробное её изучение выходит далеко за рамки знакомства с синтаксическими основами языка. Также важно помнить, что бинарная алгебра не работает с дробными числами по причине их сложного (\hyperref[text:floats]{\ref{text:floats}}) хранения.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
a = 15; //00001111
b = 11; //00001011
printf("a = %d", a);
a = a << 3; // 15 * 8
printf("a = %d", a); //a = 120; //01111000
printf("b = %d", b);
b = b >> 2; // 11 / 4
printf("b = %d", b); //b = 2; //00000010
\end{lstlisting}
\end{figure}
Применять бинарную алгебру можно и в больших проектах, работающих со сложными высокоуровневыми абстракциями. Помимо этого важно помнить, что поддержка бинарных операций есть в подавляющем числе языков программирования. Используя бинарную алгебру можно создавать оптимальные протоколы передачи данных и/или алгоритмы хранения и обработки.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
a = 15
a = 120
b = 11
b = 2
$
\end{verbatim}
\end{figure}
Тема битовых операций постепенно теряет свою актуальность, в связи с развитием технологий, скоростей, объёмов. Скорее всего, битовые операции в ближайшем будущем перейдут в разряд узкоспециальных знаний и будут применяться только при программировании микроконтроллеров, несмотря на то, что работа с двоичным представлением чисел открывает перед программистом широкий простор к оптимизации и ускорению собственного кода.