375 lines
40 KiB
TeX
375 lines
40 KiB
TeX
\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{Тип данных - указатель.} Как было сказано - переменная это именованный контейнер. У каждого такого контейнера есть свой собственный адрес в оперативной памяти. Язык С позволяет узнать этот адрес и работать с ним. Оператор взятия адреса это знак амперсанд (\&), написанный перед именем переменной. То есть у любой переменной всегда есть значение и адрес где это значение хранится (немного подробнее на стр. \pageref{fig:dereference}). Для вывода в консоль адреса используется специальный заполнитель - \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}
|
||
\label{code:programswap}
|
||
\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}
|
||
Тема битовых операций постепенно теряет свою актуальность, в связи с развитием технологий, скоростей, объёмов. Скорее всего, битовые операции в ближайшем будущем перейдут в разряд узкоспециальных знаний и будут применяться только при программировании микроконтроллеров, несмотря на то, что работа с двоичным представлением чисел открывает перед программистом широкий простор к оптимизации и ускорению собственного кода.
|