minor arrays fixes

This commit is contained in:
ivan-igorevich 2021-09-28 00:14:17 +03:00
parent 910ca2a953
commit 11de104fc4
3 changed files with 101 additions and 42 deletions

Binary file not shown.

138
main.tex
View File

@ -38,7 +38,6 @@
#define false 0 #define false 0
\end{lstlisting} \end{lstlisting}
Но нам пока что достаточно умения создать глобальную именованную константу. Код ниже демонстрирует, что директивы не обязательно группировать именно в начале файла, а можно использовать там, где это удобно и уместно, так мы можем объявить константу с длиной массива в начале файла, а можем прямо внутри функции \code{int main (int argc, char *argv[])}. Но нам пока что достаточно умения создать глобальную именованную константу. Код ниже демонстрирует, что директивы не обязательно группировать именно в начале файла, а можно использовать там, где это удобно и уместно, так мы можем объявить константу с длиной массива в начале файла, а можем прямо внутри функции \code{int main (int argc, char *argv[])}.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
#define ARRAY_LENGTH 50 #define ARRAY_LENGTH 50
@ -47,7 +46,12 @@
return 0; return 0;
} }
\end{lstlisting} \end{lstlisting}
\end{figure}
Результатом работы этой функции будет ожидаемое:
\begin{verbatim}
$ ./program
a = 50
\end{verbatim}
\subsection{Массивы} \subsection{Массивы}
Вступление про директивы препроцессора напрямую не связано с темой массивов, но директива \code{\#define} для объявления размера массива применяется чрезвычайно часто. Рассмотрим природу этого явления чуть позже. Вступление про директивы препроцессора напрямую не связано с темой массивов, но директива \code{\#define} для объявления размера массива применяется чрезвычайно часто. Рассмотрим природу этого явления чуть позже.
\frm{Массив это множество данных одного типа, расположенных в памяти подряд.} \frm{Массив это множество данных одного типа, расположенных в памяти подряд.}
@ -74,7 +78,7 @@
\begin{figure}[h!] \begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int nArr[100]; // An array for 100 int's; int nArr[100]; // An array for 100 int's;
float fArr[5]; // An array for 1 float's; float fArr[5]; // An array for 5 float's;
char cArr[2]; // An array for 2 char's; char cArr[2]; // An array for 2 char's;
int varElem; int varElem;
int nArr[varElem]; // Compile error! Number of elements must be constant; int nArr[varElem]; // Compile error! Number of elements must be constant;
@ -95,6 +99,7 @@
#include <stdio.h> #include <stdio.h>
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int arr[5];
int i; int i;
printf("Your array is: "); printf("Your array is: ");
for (i = 0; i < 5; i++) { for (i = 0; i < 5; i++) {
@ -103,7 +108,14 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }
\end{lstlisting}\end{figure} \end{lstlisting}
\end{figure}
Такая несложная программа даст нам следующий результат (Обратите внимание, что при такой инициализации, а точнее её отсутствии, значения внутри массива не гарантируются. В результате запуска программы на компьютере автора первые четыре индекса оказались равными нулю, а пятый принял странное отрицательное целочисленное значение):
\begin{verbatim}
$ ./program
Your array is 0 0 0 0 -497067408
\end{verbatim}
Мы научились создавать, инициализировать массивы и обращаться к его элементам. Теперь решим задачу посложнее: напишем программу, которая проверит насколько статистически хорош описанный в стандартной библиотеке (языка С) генератор псевдо-случайных чисел (функция \code{rand();}). Для такой статистической проверки нам понадобится сформировать так называемый \textit{частотный массив}, массив, в котором будет содержаться информация о том, сколько раз то или иное число появилось во множестве значений, полученном при помощи генератора псевдослучайных чисел, частота вхождения значений. Сама генерация псевдослучайных чисел происходит при помощий функции \code{rand();} которая создаёт целое число типа \code{int}. Но, поскольку целое число в таком диапазоне нам не нужно, мы его сократим при помощи оператора получения остатка от деления. Мы научились создавать, инициализировать массивы и обращаться к его элементам. Теперь решим задачу посложнее: напишем программу, которая проверит насколько статистически хорош описанный в стандартной библиотеке (языка С) генератор псевдо-случайных чисел (функция \code{rand();}). Для такой статистической проверки нам понадобится сформировать так называемый \textit{частотный массив}, массив, в котором будет содержаться информация о том, сколько раз то или иное число появилось во множестве значений, полученном при помощи генератора псевдослучайных чисел, частота вхождения значений. Сама генерация псевдослучайных чисел происходит при помощий функции \code{rand();} которая создаёт целое число типа \code{int}. Но, поскольку целое число в таком диапазоне нам не нужно, мы его сократим при помощи оператора получения остатка от деления.
\begin{figure}[h!] \begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
@ -111,7 +123,7 @@ int main(int argc, char *argv[]) {
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#define ARRAY_LENGTH 10 #define ARRAY_LENGTH 10
#define NUMBERS_AMOUNT 1000000; #define NUMBERS_AMOUNT 1000000
int main( int argc, char *argv[]){ int main( int argc, char *argv[]){
srand(time(NULL)); // initialize PRNG srand(time(NULL)); // initialize PRNG
@ -133,7 +145,24 @@ int main( int argc, char *argv[]){
} }
\end{lstlisting} \end{lstlisting}
\end{figure} \end{figure}
Обратите внимание на 14ю строку: для сгенерированного на 13й строке числа \code{0} увеличим значение в 0-й ячейке массива, для числа \code{1} - в 1-й, и т.д. Данная программа наглядно демонстрирует не только работу с массивами, но и то, что генератор псевдослучайных чисел в языке С генерирует статистически верную последовательность случайных чисел. Обратите внимание на 14ю строку: для сгенерированного на 13й строке числа \code{0} увеличим значение в 0-й ячейке массива, для числа \code{1} - в 1-й, и т.д. Данная программа наглядно демонстрирует не только работу с массивами, но и то, что генератор псевдослучайных чисел в языке С генерирует статистически верную последовательность случайных чисел. Инициализация генератора псевдослучайных чисел на восьмой строке \code{srand(time(NULL));} происходит значением текущего времени системы, то есть мы гарантируем, что начальное значение генератора будет отличаться от запуска к запуску, а значит наше исследование будет чуть более достоверным.
\begin{figure}[h!]
\begin{verbatim}
$ ./program
Number 0 generated 99955 (10.00%) times
Number 1 generated 99977 (10.00%) times
Number 2 generated 100156 (10.02%) times
Number 3 generated 99952 (10.00%) times
Number 4 generated 100212 (10.02%) times
Number 5 generated 100713 (10.07%) times
Number 6 generated 99418 ( 9.94%) times
Number 7 generated 99768 ( 9.98%) times
Number 8 generated 99918 ( 9.99%) times
Number 9 generated 99931 ( 9.99%) times
\end{verbatim}
\end{figure}
Запуск программы (даже несколько запусков) показывает, что всех значений получилось около десяти процентов, что говорит о том, что последовательность псевдослучайных чисел статистически верна.
\subsection{Идентификатор массива} \subsection{Идентификатор массива}
Это будет непросто, но мы поговорим о том, что из себя представляет идентификатор массива, чем чреват выход за пределы массива, затронем тему арифметики указателей и научимся передавать указатели на массивы в функции. Как упоминалось ранее, массив - это ссылочный тип данных. То есть в идентификаторе хранится адрес, ссылка на первый байт первого элемента массива, дальнейший доступ к элементам осуществляется посредством \textit{смещения относительно этого байта}. Таким образом запись вида \code{array[0]} говорит нам о том, что нужно взять адрес массива и сместить указатель на \code{0} элементов того типа, из которых состоит массив. Отсюда становится ясно, почему \textbf{индексирование массивов начинается с нуля}. Это будет непросто, но мы поговорим о том, что из себя представляет идентификатор массива, чем чреват выход за пределы массива, затронем тему арифметики указателей и научимся передавать указатели на массивы в функции. Как упоминалось ранее, массив - это ссылочный тип данных. То есть в идентификаторе хранится адрес, ссылка на первый байт первого элемента массива, дальнейший доступ к элементам осуществляется посредством \textit{смещения относительно этого байта}. Таким образом запись вида \code{array[0]} говорит нам о том, что нужно взять адрес массива и сместить указатель на \code{0} элементов того типа, из которых состоит массив. Отсюда становится ясно, почему \textbf{индексирование массивов начинается с нуля}.
@ -188,6 +217,7 @@ int main( int argc, char *argv[]){
Помним, что индексация массива начинается с нуля, поэтому длина массива всегда на единицу больше последнего индекса. Выведем в консоль надпись <<введите значение>>, при помощи функции \code{scanf();} считаем его и сразу привычным образом, оператором индексного доступа, положим в массив. Помним, что индексация массива начинается с нуля, поэтому длина массива всегда на единицу больше последнего индекса. Выведем в консоль надпись <<введите значение>>, при помощи функции \code{scanf();} считаем его и сразу привычным образом, оператором индексного доступа, положим в массив.
\begin{figure}[h!] \begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int arr[ARRAY_LENGTH];
int i = 0; int i = 0;
float result = 0; float result = 0;
while (i < ARRAY_LENGTH) { while (i < ARRAY_LENGTH) {
@ -207,10 +237,25 @@ for (i = 0; i < ARRAY_LENGTH; i++)
printf("\nAnd the average is: "); printf("\nAnd the average is: ");
for (i = 0; i < ARRAY_LENGTH; i++) for (i = 0; i < ARRAY_LENGTH; i++)
result += *(arr + i); result += *(arr + i);
printf("%f\n", result / ARRAY_LENGTH);
\end{lstlisting} \end{lstlisting}
\end{figure} \end{figure}
Как вы видите, некоторые подсчеты программа выполняет за нас - мы прибавляем к указателю единицу, двойку, тройку и т.д, а программа понимает, что надо взять не следующий по счёту байт, а следующий указатель. Так как в данном примере мы используем массив в котором хранятся значения типа \code{int}, а как вы помните \code{int} в подавляющем большинстве случаев - это четыре байта, то при увеличении указателя на единицу, мы обратимся к области памяти находящейся на четыре байта дальше идентификатора, при увеличении на двойку на восемь байт и так далее. Подсчитать среднее арифметическое не составит труда, этот алгоритм нам знаком со средней школы. Далее при помощи функции \code{printf();} выведем в консоль среднее арифметическое. Запустим, повводим цифры и убедимся что все работает. Как вы видите, некоторые подсчеты программа выполняет за нас - мы прибавляем к указателю единицу, двойку, тройку и т.д, а программа понимает, что надо взять не следующий по счёту байт, а следующий указатель. Так как в данном примере мы используем массив в котором хранятся значения типа \code{int}, а как вы помните \code{int} в подавляющем большинстве случаев - это четыре байта, то при увеличении указателя на единицу, мы обратимся к области памяти находящейся на четыре байта дальше идентификатора, при увеличении на двойку на восемь байт и так далее. Подсчитать среднее арифметическое не составит труда, этот алгоритм нам знаком со средней школы. Далее при помощи функции \code{printf();} выведем в консоль среднее арифметическое. Запустим, повводим цифры и убедимся что все работает.
\begin{verbatim}
$ ./program
Enter value 0: 1
Enter value 1: 2
Enter value 2: 3
Enter value 3: 4
Enter value 4: 5
Enter value 5: 6
Enter value 6: 7
Enter value 7: 8
Enter value 8: 9
Enter value 9: 10
Your array is: 1 2 3 4 5 6 7 8 9 10
And the average is: 5.500000
\end{verbatim}
Внимательный читатель мог заметить, что мы применяем операцию \textit{разыменования}. Что происходит, когда мы таким образом обращаемся к массиву? Операция разыменования получает доступ к значению, находящемуся по адресу. Адресс массива - это адрес его первого элемента, поэтому конструкция \code{*arr} вернёт значение нулевого элемента массива. А прибавление значений к этому указателю будет смещать его также, как это делает оператор квадратных скобок. Внимательный читатель мог заметить, что мы применяем операцию \textit{разыменования}. Что происходит, когда мы таким образом обращаемся к массиву? Операция разыменования получает доступ к значению, находящемуся по адресу. Адресс массива - это адрес его первого элемента, поэтому конструкция \code{*arr} вернёт значение нулевого элемента массива. А прибавление значений к этому указателю будет смещать его также, как это делает оператор квадратных скобок.
Как уже упоминалось, идентификатор массива - это не обычный указатель. Обычный указатель хранит в себе адрес какой-то другой переменной, и сам где-то хранится. Указатель на начало массива хранит в себе адрес массива, то есть адрес его нулевого элемента, и сам этот указатель находится в этом самом месте. На первый взгляд сложновато? Но пусть Вас это не сбивает с толку, на деле всё не так жутко. На деле это означает, что при передаче массива (читай идентификатора массива) в функцию в качестве аргумента, мы не должны использовать оператор взятия адреса, поскольку идентификатор массива сам по себе является указателем на собственное начало. Это открывает для нас широкие возможности по написанию функций, работающих с массивами данных. В только что написанной нами программе оформим вывод массива на экран и поиск среднего арифметического в виде функции. Опишем функции \code{printArray()} и \code{average()} в которые передадим указатель на массив и его длину, т.к. в массиве не содержится сведений о его размере. Как уже упоминалось, идентификатор массива - это не обычный указатель. Обычный указатель хранит в себе адрес какой-то другой переменной, и сам где-то хранится. Указатель на начало массива хранит в себе адрес массива, то есть адрес его нулевого элемента, и сам этот указатель находится в этом самом месте. На первый взгляд сложновато? Но пусть Вас это не сбивает с толку, на деле всё не так жутко. На деле это означает, что при передаче массива (читай идентификатора массива) в функцию в качестве аргумента, мы не должны использовать оператор взятия адреса, поскольку идентификатор массива сам по себе является указателем на собственное начало. Это открывает для нас широкие возможности по написанию функций, работающих с массивами данных. В только что написанной нами программе оформим вывод массива на экран и поиск среднего арифметического в виде функции. Опишем функции \code{printArray()} и \code{average()} в которые передадим указатель на массив и его длину, т.к. в массиве не содержится сведений о его размере.
@ -239,6 +284,7 @@ float average(int* array, int length) {
\lstinputlisting[language=C,style=CCodeStyle]{../sources/arrayaverage.c} \lstinputlisting[language=C,style=CCodeStyle]{../sources/arrayaverage.c}
\label{code:arrayaverage} \label{code:arrayaverage}
\end{figure} \end{figure}
\newpage
\subsection{Многомерные массивы} \subsection{Многомерные массивы}
Массив в языке С может иметь сколько угодно измерений. Все массивы, с которыми мы имели дело до этого момента - одномерные, их легко визуализировать в виде простого перечисления элементов, возможно, как строки или как таблицы, состоящей из одной строки. Самые распространённые многомерные массивы - это двумерные и трёхмерные, которые легко себе представить в виде таблицы или куба соответственно. Итак, массив это структура, содержащая элементы. Двумерный массив - это массив из массивов, содержащих элементы. Трёхмерный - это массив из массивов, содержащих массивы, которые содержат элементы. И так далее. В массиве могут находиться любые типы данных, мы, для удобства, будем рассматривать работу массивов с числами. Массив в языке С может иметь сколько угодно измерений. Все массивы, с которыми мы имели дело до этого момента - одномерные, их легко визуализировать в виде простого перечисления элементов, возможно, как строки или как таблицы, состоящей из одной строки. Самые распространённые многомерные массивы - это двумерные и трёхмерные, которые легко себе представить в виде таблицы или куба соответственно. Итак, массив это структура, содержащая элементы. Двумерный массив - это массив из массивов, содержащих элементы. Трёхмерный - это массив из массивов, содержащих массивы, которые содержат элементы. И так далее. В массиве могут находиться любые типы данных, мы, для удобства, будем рассматривать работу массивов с числами.
@ -251,6 +297,23 @@ float average(int* array, int length) {
int twoDimensional[5][5]; int twoDimensional[5][5];
\end{lstlisting} \end{lstlisting}
\begin{center}
\begin{tabular}{||c|c|c|c|c||}
\hline
\hline
0,0 & 0,1 & 0,2 & 0,3 & 0,4 \\
\hline
1,0 & 1,1 & 1,2 & 1,3 & 1,4 \\
\hline
2,0 & 2,1 & 2,2 & 2,3 & 2,4 \\
\hline
3,0 & 3,1 & 3,2 & 3,3 & 3,4 \\
\hline
4,0 & 4,1 & 4,2 & 4,3 & 4,4 \\
\hline
\hline
\end{tabular}
\end{center}
\columnbreak \columnbreak
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int threeDimensional[3][3][3]; int threeDimensional[3][3][3];
@ -258,44 +321,39 @@ int threeDimensional[3][3][3];
\begin{tikzpicture}[every node/.style={minimum size=1cm},on grid] \begin{tikzpicture}[every node/.style={minimum size=1cm},on grid]
\begin{scope}[every node/.append style={yslant=-0.5},yslant=-0.5] \begin{scope}[every node/.append style={yslant=-0.5},yslant=-0.5]
\shade[right color=gray!10, left color=black!50] (0,0) rectangle +(3,3); \node at (0.5,2.5) {2,0,0};
\node at (0.5,2.5) {9}; \node at (1.5,2.5) {1,0,0};
\node at (1.5,2.5) {7}; \node at (2.5,2.5) {0,0,0};
\node at (2.5,2.5) {1}; \node at (0.5,1.5) {2,1,0};
\node at (0.5,1.5) {2}; \node at (1.5,1.5) {1,1,0};
\node at (1.5,1.5) {4}; \node at (2.5,1.5) {0,1,0};
\node at (2.5,1.5) {8}; \node at (0.5,0.5) {2,2,0};
\node at (0.5,0.5) {5}; \node at (1.5,0.5) {1,2,0};
\node at (1.5,0.5) {3}; \node at (2.5,0.5) {0,2,0};
\node at (2.5,0.5) {6};
\draw (0,0) grid (3,3); \draw (0,0) grid (3,3);
\end{scope} \end{scope}
\begin{scope}[every node/.append style={yslant=0.5},yslant=0.5] \begin{scope}[every node/.append style={yslant=0.5},yslant=0.5]
\shade[right color=gray!70,left color=gray!10] (3,-3) rectangle +(3,3); \node at (3.5,-0.5) {0,0,0};
\node at (3.5,-0.5) {3}; \node at (4.5,-0.5) {0,0,1};
\node at (4.5,-0.5) {9}; \node at (5.5,-0.5) {0,0,2};
\node at (5.5,-0.5) {7}; \node at (3.5,-1.5) {0,1,0};
\node at (3.5,-1.5) {6}; \node at (4.5,-1.5) {0,1,1};
\node at (4.5,-1.5) {1}; \node at (5.5,-1.5) {0,1,2};
\node at (5.5,-1.5) {5}; \node at (3.5,-2.5) {0,2,0};
\node at (3.5,-2.5) {8}; \node at (4.5,-2.5) {0,2,1};
\node at (4.5,-2.5) {2}; \node at (5.5,-2.5) {0,2,2};
\node at (5.5,-2.5) {4};
\draw (3,-3) grid (6,0); \draw (3,-3) grid (6,0);
\end{scope} \end{scope}
\begin{scope}[every node/.append style={ \begin{scope}[every node/.append style={yslant=0.5,xslant=-1},yslant=0.5,xslant=-1]
yslant=0.5,xslant=-1},yslant=0.5,xslant=-1 \node at (3.5,2.5) {2,0,0};
] \node at (3.5,1.5) {1,0,0};
\shade[bottom color=gray!10, top color=black!80] (6,3) rectangle +(-3,-3); \node at (3.5,0.5) {0,0,0};
\node at (3.5,2.5) {1}; \node at (4.5,2.5) {2,0,1};
\node at (3.5,1.5) {4}; \node at (4.5,1.5) {1,0,1};
\node at (3.5,0.5) {7}; \node at (4.5,0.5) {0,0,1};
\node at (4.5,2.5) {5}; \node at (5.5,2.5) {2,1,2};
\node at (4.5,1.5) {6}; \node at (5.5,1.5) {1,1,2};
\node at (4.5,0.5) {8}; \node at (5.5,0.5) {0,1,2};
\node at (5.5,2.5) {2};
\node at (5.5,1.5) {3};
\node at (5.5,0.5) {9};
\draw (3,0) grid (6,3); \draw (3,0) grid (6,3);
\end{scope} \end{scope}
\end{tikzpicture} \end{tikzpicture}

View File

@ -18,11 +18,12 @@ float average(int* array, int length) {
int main(int argc, const char** argv) { int main(int argc, const char** argv) {
#define ARRAY_LENGTH 10 #define ARRAY_LENGTH 10
int arr[ARRAY_LENGTH];
int i = 0; int i = 0;
float result = 0; float result = 0;
while (i < ARRAY_LENGTH) { while (i < ARRAY_LENGTH) {
printf("Enter value %d:", i); printf("Enter value %d:", i);
scanf("%d", arr[i]); scanf("%d", &arr[i]);
i++; i++;
} }
printf("Our array is: "); printf("Our array is: ");