listings fix (removed cyrillic from code)

This commit is contained in:
Ivan I. Ovchinnikov 2021-08-23 12:20:03 +03:00
parent 4e6f2821cd
commit e100bef95d
2 changed files with 83 additions and 83 deletions

Binary file not shown.

View File

@ -40,15 +40,15 @@ $ ./program
$ $
\end{verbatim} \end{verbatim}
\paragraph{Числа с плавающей точкой (дробные)} представлены двумя типами: четырёхбайтный \code{float} и восьмибайтный \code{double} (также называемый long float). Хранятся в памяти в неявном виде, а разделённые на мантиссу экспоненту и знак, что делает их одними из самых сложных в работе\footnote{Здесь имеется ввиду внутренняя работа самого компьютера, а не работа с такими типами с точки зрения программиста. Именно из-за сложности, многие старые процессорные архитектуры не имели возможности работать с переменными такого типа}. При работе с числами с плавающей точкой нужно обращать особенное внимание на тип переменной, поскольку сравнение внешне одинаковых чисел разных типов с вероятностью 99\% даст ложный результат. \paragraph{Числа с плавающей точкой (дробные)} представлены двумя типами: четырёхбайтный \code{float} и восьмибайтный \code{double} (также называемый long float). Хранятся в памяти в неявном виде, а разделённые на мантиссу экспоненту и знак, что делает их одними из самых сложных в работе\footnote{Здесь имеется ввиду внутренняя работа самого компьютера, а не работа с такими типами с точки зрения программиста. Именно из-за сложности, многие старые процессорные архитектуры не имели возможности работать с переменными такого типа}. При работе с числами с плавающей точкой нужно обращать особенное внимание на тип переменной, поскольку сравнение внешне одинаковых чисел разных типов с вероятностью 99\% даст ложный результат.
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% float real = 5,345f; // 4 bytes float real = 5,345f; // 4 bytes
% double realdouble = 5,345; // 8 bytes double realdouble = 5,345; // 8 bytes
% printf("float and double: %d\n", real == realdouble); printf("float and double: %d\n", real == realdouble);
% int a = 10; int a = 10;
% int b = 10; int b = 10;
% printf("integers: %d\n", a == b); printf("integers: %d\n", a == b);
% \end{lstlisting} \end{lstlisting}
Запустим код и убедимся в этом: Запустим код и убедимся в этом:
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
@ -57,92 +57,92 @@ integers: 1
$ $
\end{verbatim} \end{verbatim}
\paragraph{Тип данных - указатель.} Как было сказано - переменная это именованный контейнер. У каждого такого контейнера есть свой собственный адрес в оперативной памяти. Язык С позволяет узнать этот адрес и работать с ним. Оператор взятия адреса это знак амперсанд (\&), написанный перед именем переменной. То есть у любой переменной всегда есть значение и адрес где это значение хранится. Для вывода в консоль адреса используется специальный заполнитель - \code{\%p}. \paragraph{Тип данных - указатель.} Как было сказано - переменная это именованный контейнер. У каждого такого контейнера есть свой собственный адрес в оперативной памяти. Язык С позволяет узнать этот адрес и работать с ним. Оператор взятия адреса это знак амперсанд (\&), написанный перед именем переменной. То есть у любой переменной всегда есть значение и адрес где это значение хранится. Для вывода в консоль адреса используется специальный заполнитель - \code{\%p}.
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% printf("Переменная а имеет значение: %d \n", a); printf("Variable a has value: %d \n", a);
% printf("Переменная а хранится по адресу: %p \n", &a); printf("Variable a stored at: %p \n", &a);
% \end{lstlisting} \end{lstlisting}
При неоднократном запуске кода можно обратить внимание, что первые цифры в адресе всегда остаются неизменными, а последние меняются редко, это связано с тем, что в современных операционных системах пользователю для его работы чаще всего выделяется некоторое адресное пространство, которое потом просто переиспользуется и перезаписывается. При запуске точно такого же кода на Вашем компьютере, Вы увидите, что адрес хранения переменной наверняка будет отличаться, это нормально. При неоднократном запуске кода можно обратить внимание, что первые цифры в адресе всегда остаются неизменными, а последние меняются редко, это связано с тем, что в современных операционных системах пользователю для его работы чаще всего выделяется некоторое адресное пространство, которое потом просто переиспользуется и перезаписывается. При запуске точно такого же кода на Вашем компьютере, Вы увидите, что адрес хранения переменной наверняка будет отличаться, это нормально.
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
Переменная а имеет значение: 10 Variable a has value: 10
Переменная а хранится по адресу: 0x7ffe6aa136cf Variable a stored at: 0x7ffe6aa136cf
$ $
\end{verbatim} \end{verbatim}
\subsection{Базовая арифметика} \subsection{Базовая арифметика}
Раз уж в предыдущем разделе мы коснулись арифметических выражений, поговорим немного об арифметике. Раз уж в предыдущем разделе мы коснулись арифметических выражений, поговорим немного об арифметике.
\paragraph{Простые арифметические операции.} В языке С поддерживаются все базовые арифметические операции, такие как сложение, вычитание, умножение, деление. Операции бинарной арифметики (булевой алгебры), такие как \code{И}, \code{ИЛИ}, \code{НЕ}, \code{ИСКЛЮЧАЮЩЕЕ ИЛИ}, \code{СДВИГИ}. А также все вышеперечисленные операции с последующим присваиванием в первую переменную. Для начала, инициализируем переменную типа \code{int} значением, например, \code{70}, и выведем ее в консоль. Мы можем производить с этой переменной все привычные базовые арифметические манипуляции, ровно также, как мы можем производить эти манипуляции с литералом, который ей присваивается: \paragraph{Простые арифметические операции.} В языке С поддерживаются все базовые арифметические операции, такие как сложение, вычитание, умножение, деление. Операции бинарной арифметики (булевой алгебры), такие как \code{И}, \code{ИЛИ}, \code{НЕ}, \code{ИСКЛЮЧАЮЩЕЕ ИЛИ}, \code{СДВИГИ}. А также все вышеперечисленные операции с последующим присваиванием в первую переменную. Для начала, инициализируем переменную типа \code{int} значением, например, \code{70}, и выведем ее в консоль. Мы можем производить с этой переменной все привычные базовые арифметические манипуляции, ровно также, как мы можем производить эти манипуляции с литералом, который ей присваивается:
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% int variable = 70; int variable = 70;
% printf("Переменная variable = %d\n", variable); printf("The variable = %d\n", variable);
% printf("Переменная variable = %d\n", variable + 10); printf("The variable = %d\n", variable + 10);
% variable = variable + 50; variable = variable + 50;
% printf("Переменная variable = %d\n", variable); printf("The variable = %d\n", variable);
% variable = 123 + 50 * 12; variable = 123 + 50 * 12;
% printf("Переменная variable = %d\n", variable); printf("The variable = %d\n", variable);
% \end{lstlisting} \end{lstlisting}
То есть, нам доступны операции сложения, умножения, вычитания и деления. Как видно в результате работы программы, есть прямая зависимость между действительным значением переменной и порядком присваивания в неё значения, так в третьем выводе значение равно 120, то есть $70 + 50$, а значит во втором выводе присваивания нового значения $70 + 10$ в переменную \code{variable} не произошло. То есть, нам доступны операции сложения, умножения, вычитания и деления. Как видно в результате работы программы, есть прямая зависимость между действительным значением переменной и порядком присваивания в неё значения, так в третьем выводе значение равно 120, то есть $70 + 50$, а значит во втором выводе присваивания нового значения $70 + 10$ в переменную \code{variable} не произошло.
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
Переменная variable = 70 The variable = 70
Переменная variable = 80 The variable = 80
Переменная variable = 120 The variable = 120
Переменная variable = 723 The variable = 723
$ $
\end{verbatim} \end{verbatim}
\paragraph{Оператор деления} заслуживает особенного внимания, поскольку у него есть два режима работы, отличающие этот оператор от привычной нам арифметики: если мы производим операции с целыми числами такими как \code{int}, \code{short} или \code{char} оператор деления всегда будет возвращать только целые числа, \textbf{отбросив дробную часть}. Это происходит из-за оптимизаций компилятора, то есть если операнды - это целые числа, то и результатом по мнению компьютера может быть только целое число, а из-за строгости типизации мы не можем положить в целочисленную переменную значение с плавающей точкой. \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} вполне может содержать число без дробной части. Из-за этого языки С/С++ считаются слабо типизированными. Таким образом, отслеживание точности вычислений полностью возлагается на плечи программиста. Важно, что компилятор нам в этом помогает, хоть и не делает всю работу за нас. Компилятор, при преобразовании операций автоматически приводит типы операндов к наиболее подходящему и широкому. То есть, если мы, например, складываем два числа \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{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% variable = 10; variable = 10;
% variable = variable / 3; variable = variable / 3;
% printf("Переменная variable = %d\n", variable); printf("The variable = %d\n", variable);
% float var = 10; float var = 10;
% var = var / 3; var = var / 3;
% printf("Переменная var = %f\n", var); printf("The var = %f\n", var);
% \end{lstlisting} \end{lstlisting}
При использовании оператора деления с целочисленными операндами теряется точность вычислений, что недопустимо. В примере выше, обратите внимание, что переменная \code{variable} не была инициализирована, а ей просто было присвоено значение. Это сделано потому, что данный участок кода является продолжением кода примеров простой арифметики из предыдущего параграфа. При использовании оператора деления с целочисленными операндами теряется точность вычислений, что недопустимо.
\frm{Чтобы оператор деления отработал в не целочисленном режиме, нужно, чтобы хотя бы один операнд был не целочисленным.} \frm{Чтобы оператор деления отработал в не целочисленном режиме, нужно, чтобы хотя бы один операнд был не целочисленным.}
Чаще всего целочисленные переменные используют в качестве счётчиков, индексов и других вспомогательных переменных, поэтому математические операции с ними весьма распространены. Чаще всего целочисленные переменные используют в качестве счётчиков, индексов и других вспомогательных переменных, поэтому математические операции с ними весьма распространены.
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
Переменная variable = 3 The variable = 3
Переменная var = 3.333333 The var = 3.333333
$ $
\end{verbatim} \end{verbatim}
\paragraph{Деление по модулю.} Также особенного внимания заслуживает оператор получения остатка от деления, иногда называемый оператором взятия по модулю. Записывается как символ \code{\%} и возвращает остаток от целочисленного деления первого числа на второе: \paragraph{Деление по модулю.} Также особенного внимания заслуживает оператор получения остатка от деления, иногда называемый оператором взятия по модулю. Записывается как символ \code{\%} и возвращает остаток от целочисленного деления первого числа на второе:
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% int remain = variable % 5; int remain = variable % 5;
% printf("Остаток от деления %d на %d: %d\n", variable, 5, remain); printf("Division remainder of %d by %d: %d\n", variable, 5, remain);
% variable = variable + 50; variable = variable + 50;
% printf("Переменная variable = %d\n", variable); printf("The variable = %d\n", variable);
% variable += 50; variable += 50;
% printf("Переменная variable = %d\n", variable); printf("The variable = %d\n", variable);
% \end{lstlisting} \end{lstlisting}
Любые арифметические операции можно выполнить с последующим присваиванием в первую переменную. То есть это означает, что запись вида \code{variable = variable + 50;} можно сократить до \code{variable += 50;} и запустив следующий код мы можем убедиться, что начальное значение переменной увеличилось сначала на 50, а затем ещё на 50. Любые арифметические операции можно выполнить с последующим присваиванием в первую переменную. То есть это означает, что запись вида \code{variable = variable + 50;} можно сократить до \code{variable += 50;} и запустив следующий код мы можем убедиться, что начальное значение переменной увеличилось сначала на 50, а затем ещё на 50.
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
Остаток от деления 3 на 5: 3 Division remainder of 3 by 5: 3
Переменная variable = 53 The variable = 53
Переменная variable = 103 The variable = 103
$ $
\end{verbatim} \end{verbatim}
\paragraph{Инкремент и декремент.} Так, бегло рассмотрев арифметику в языке С нельзя не упомянуть об операторах увеличения и уменьшения значения переменной на единицу с последующим присваиванием. Они называются операторами инкремента (\code{++}) и декремента (\code{\textminus\textminus}). Это унарный оператор, поэтому записывается со своим операндом строго без пробелов: \code{variable++;} и редко используется как самостоятельный оператор на отдельной строке. У операторов инкремента и декремента есть два вида записи: префиксный и постфиксный. Их отличает время применения текущего значения и его изменения. При постфиксной записи, сначала происходит применение текущего результата, а затем его изменение, а при префиксной записи - сначала изменение, а затем применение. Например: \paragraph{Инкремент и декремент.} Так, бегло рассмотрев арифметику в языке С нельзя не упомянуть об операторах увеличения и уменьшения значения переменной на единицу с последующим присваиванием. Они называются операторами инкремента (\code{++}) и декремента (\code{\textminus\textminus}). Это унарный оператор, поэтому записывается со своим операндом строго без пробелов: \code{variable++;} и редко используется как самостоятельный оператор на отдельной строке. У операторов инкремента и декремента есть два вида записи: префиксный и постфиксный. Их отличает время применения текущего значения и его изменения. При постфиксной записи, сначала происходит применение текущего результата, а затем его изменение, а при префиксной записи - сначала изменение, а затем применение. Например:
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% variable = 50; variable = 50;
% printf("1. Пост-инкремент: %d\n", variable++); printf("1. Postfix increment: %d\n", variable++);
% printf("1. Следующая строка: %d\n", variable); printf("1. Next line of code: %d\n", variable);
% variable = 50; variable = 50;
% printf("2. Пре-инкремент: %d\n", ++variable); printf("2. Prefix increment: %d\n", ++variable);
% printf("2. Следующая строка: %d\n", variable); printf("2. Next line of code: %d\n", variable);
% \end{lstlisting} \end{lstlisting}
В результате выполнения этого кода мы можем видеть на экране следующий результат: В результате выполнения этого кода мы можем видеть на экране следующий результат:
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
1. Пост-инкремент: 50 1. Postfix increment: 50
1. Следующая строка: 51 1. Next line of code: 51
2. Пре-инкремент: 51 2. Prefix increment: 51
2. Следующая строка: 51 2. Next line of code: 51
$ $
\end{verbatim} \end{verbatim}
\subsection{Булева алгебра и двоичные вычисления} \subsection{Булева алгебра и двоичные вычисления}
@ -221,19 +221,19 @@ $
\end{itemize} \end{itemize}
На основе этих знаний мы можем для примера написать программу, меняющую местами значения переменных без использования третьей, вспомогательной и быть уверенными, что переполнения переменных не произойдёт, как это могло бы произойти, например, при использовании сложения и обратного вычитания. Объявим две переменных \code{a} и \code{b}, присвоим им значения и выведем их в консоль. Также подготовим вывод измененных значений \code{a} и \code{b} в консоль: На основе этих знаний мы можем для примера написать программу, меняющую местами значения переменных без использования третьей, вспомогательной и быть уверенными, что переполнения переменных не произойдёт, как это могло бы произойти, например, при использовании сложения и обратного вычитания. Объявим две переменных \code{a} и \code{b}, присвоим им значения и выведем их в консоль. Также подготовим вывод измененных значений \code{a} и \code{b} в консоль:
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% char a = 11; char a = 11;
% char b = 15; char b = 15;
% printf("a = %d, b = %d\n", a, b); printf("a = %d, b = %d\n", a, b);
% // here will be the swapping algorithm // here will be the swapping algorithm
% printf("a = %d, b = %d\n", a, b); printf("a = %d, b = %d\n", a, b);
% \end{lstlisting} \end{lstlisting}
Далее, напишем некую конструкцию, которая при детальном изучении не представляет из себя никакой магии. В переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в переменную \code{b} нужно будет записать результат вычисления \code{b \^{} a} и наконец в переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в коде ниже будет приведена сразу сокращённая запись: Далее, напишем некую конструкцию, которая при детальном изучении не представляет из себя никакой магии. В переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в переменную \code{b} нужно будет записать результат вычисления \code{b \^{} a} и наконец в переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в коде ниже будет приведена сразу сокращённая запись:
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% a ^= b; a ^= b;
% b ^= a; b ^= a;
% a ^= b; a ^= b;
% \end{lstlisting} \end{lstlisting}
Нужно сразу оговориться, что этот алгоритм может некорректно работать с одинаковыми и отрицательными числами, это будет зависеть от компилятора, поэтому, если включать этот алгоритм в состав более сложных, лучше осуществлять дополнительные проверки. Вывод этой конкретной программы будет следующим: Нужно сразу оговориться, что этот алгоритм может некорректно работать с одинаковыми и отрицательными числами, это будет зависеть от компилятора, поэтому, если включать этот алгоритм в состав более сложных, лучше осуществлять дополнительные проверки. Вывод этой конкретной программы будет следующим:
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
@ -278,16 +278,16 @@ a = a ^ b; //00001111
\paragraph{Операции сдвига} бывают логические, арифметические и циклические. В языке С реализован логический сдвиг, то есть недостающие разряды при сдвиге заполняются нулями. Итак, допустим, что нам нужно переменную \code{a} сдвинуть влево на \code{3} бита, на самом деле это означает что мы переменную \code{a} умножим на $2^3$. А переменную \code{b} мы сдвинем вправо на \code{2} бита, это означает, что мы переменную \code{b} разделим на $2^2$, при этом важно упомянуть, что будет произведено целочисленное деление. \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}$.} \frm{\textbf{Сдвиг влево} числа k на n - это \textit{умножение} $k*2^n$. \textbf{Сдвиг вправо} числа k на n - это \textit{целочисленное деление} $\frac{k}{2^n}$.}
Это тоже самое что записать $a * 8$ и $b / 4$. Просто на маломощных компьютерах выполнится это гораздо быстрее. Бинарная алгебра это большая и сложная тема. Это тоже самое что записать $a * 8$ и $b / 4$. Просто на маломощных компьютерах выполнится это гораздо быстрее. Бинарная алгебра это большая и сложная тема.
% \begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
% a = 15; //00001111 a = 15; //00001111
% b = 11; //00001011 b = 11; //00001011
% printf("a = %d", a); printf("a = %d", a);
% a = a << 3; // 15 * 8 a = a << 3; // 15 * 8
% printf("a = %d", a); //a = 120; //01111000 printf("a = %d", a); //a = 120; //01111000
% printf("b = %d", b); printf("b = %d", b);
% b = b >> 2; // 11 / 4 b = b >> 2; // 11 / 4
% printf("b = %d", b); //b = 2; //00000010 printf("b = %d", b); //b = 2; //00000010
% \end{lstlisting} \end{lstlisting}
Применять бинарную алгебру можно и в больших проектах, работающих со сложными высокоуровневыми абстракциями. Поддержка бинарных операций есть в подавляющем числе языков программирования. Применять бинарную алгебру можно и в больших проектах, работающих со сложными высокоуровневыми абстракциями. Поддержка бинарных операций есть в подавляющем числе языков программирования.
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program