October 28, 2018

Допустим, перед нами

Допустим, перед нами стоит задача сгенерировать одномерный массив, заполнить его рандомными (случайными) числами в диапазоне от 7 до 14. А затем заменить в массиве элементы кратные 7 на 0.
Разберем подробно возможную реализации на языке Си.


stdio.h (от англ. standard input В этом заголовочном файле нам понадобится, например, методы printf().


stdlib.h заголовочный файл стандартной библиотеки языка Си, который содержит в себе функции, занимающиеся выделением памяти, контролем процесса выполнения программы, преобразованием типов и другие. Заголовок вполне совместим с C++ и известен в нём как cstdlib. Название stdlib расшифровывается как standard library (стандартная библиотека). В этом заголовочном файле нам понадобятся два метода для генерации псевдослучайных последовательностей:
rand() – генерирует псевдослучайное значение
srand() – устанавливает начальное значение генератора псевдослучайных чисел


time.h заголовочный файл стандартной библиотеки языка программирования C, содержащий типы и функции для работы с датой и временем.
В этом заголовочном файле нам понадобится time_t time(time_t *tp)
Возвращает текущее календарное время или 1, если это время не известно. Если указатель tp не равен NULL, то возвращаемое значение записывается также и в *tp

typedef int TArray[100];
Язык С позволяет определять имена новых типов данных с помощью ключевого слова typedef. На самом деле здесь не создается новый тип данных, а определяется новое имя существующему типу. Он позволяет облегчить создание машинно-независимых программ. Единственное, что потребуется при переходе на другую платформу, – это изменить оператор typedef. Он также может помочь документировать код, позволяя назначать содержательные имена стандартным типам данных. Стандартный вид оператора typedef следующий:

typedef тип имя;

где тип это любой существующий тип данных, а имя – это новое имя для данного типа. Новое имя определяется в дополнение к существующему имени типа, а не замещает его. Например, можно создать новое имя для float, используя

typedef float balance;

Данный оператор сообщает компилятору о необходимости распознавания balance как другого имени для float. Далее можно создать вещественную переменную, используя balance:
balance past_due;

Здесь past_due – это вещественная переменная типа balance, другими словами – типа float. Можно использовать typedef для создания имен для более сложных типов. Например:

typedef struct {
float due;
int over_due;
char name[40];
} client; /* здесь client – это имя нового типа */

client clist[NUM_CLIENTS]; /* определение массива структур типа client */

Использование typedef может помочь при создании более легкого для чтения и более переносимого кода. Но надо помнить, что на самом деле не создаются никакие новые типы данных.
В нашем же случае мы через typedef делаем для массива более удобный тип TArray. Там же определяем размерность массива в 100 элементов (максимум для данного массива).

Далее начнем рассмотрение программы с главного блока main:

srand( time( NULL ) );
В самом начале мы видим srand(time(NULL)); Исходя из объяснения выше, вы уже могли догадаться о том, что это строка устанавливает начальное значение генератора псевдослучайных чисел в зависимости от переданного её текущего времени. В качестве аргумента для srand() предается функция time( NULL ) ,которая считывает текущее время на компьютере и возвращает кол-во секунд прошедших от полуночи 1 января 1970 преобразованных в тип unsigned int позволяя иметь таким образом определенную разновидность в генерации случайных чисел.

int N = 10;
Не особо удачное название для переменной. Ибо очень не информативно. Но для маленькой сферической программы в вакууме вполне пойдет 🙂
Это будет инициализация размерности массива.

TArray arr;
Собственно, объявление самого массива, который в дальнейшем будем инициализировать рандомными ( случайными ) числами.

initArray( arr, N );
Вызываем метод инициализации массива случайными числами.
Рассмотрим отдельно этот метод, реализация которого написана выше основного main блока программы.

void initArray(TArray arr, int N){
for(int i = 0; i < N; i++){ arr[i] = getRandom(7, 14); }
}

void – так как метод ничего не возвращает (аналог процедуры для тех, кто знаком с Pascal Далее initArray – название метода. Желательно называть так, чтобы по одному названию было понятно то, что делает данный метод. (TArray arr, int N) – параметры, которые принимает метод. В данном случае он принимает ссылку на массив, переменную размера массива.
for(int i = 0; i < N; i++) стандартный для Си-подобных языков цикл FOR, индекс которого пробегает значения от 0 до N - 1 включительно (N не включает, да и не нужно, иначе выйдет за диапазон ;) ).
Внутри цикла написано arr[i] = getRandom(7, 14);
Это говорит нам о том, что каждому i-му элементу массива присваивается что-то интересное, что возвращает метод getRandom(7, 14) с параметрами 7 и 14.

Метод getRandom()

int getRandom(int start, int end){
return start + rand() % (end – start);
}
Вначале у нас стоит int. Это говорит о том, что метод возвращает значения типа int (integer – целое число)
getRandom – название метода. Может быть любым, но желательно называть так, чтобы мы четко понимали, что метод дает возможность получить целое число. (int start, int end) параметры метода вместе с их типами. Параметры метода – целые числа.
start – начало промежутка
end – конец промежутка
return ключевое слово метода, после которого идет то, что нужно вернуть из метода “наружу”, то есть результат работы функции.
start + rand() % (end – start);
Функция rand() генерирует случайные (не совсем случайные, но будем считать их такими) числа, возвращает псевдослучайное целое число в диапазоне от 0 до RAND_MAX.
Это число генерируется алгоритмом, который возвращает последовательность псевдо-случайных чисел. Этот алгоритм использует своего рода семя число, для создания серий случайных чисел. Именно это семя мы передавали в srand() в качестве текущего времени, которое хранится в переменной типа unsigned int и которое при каждом запуске программы будет разным. То есть, если семя всегда будет одно и то же, то сгенерированная последовательность чисел не будет меняться, исчезнет фактор стохастичности.

Итак, распишем подробнее:
0 < rand() < RAND_MAX
Так как знак процентов “%” в С/C++ обозначает остаток от деления ( более начну эту операцию называют деление по модулю )
А остаток от деления не может превышать то, на что мы делим (не превышает модуль).
Пример:
Будем делить натуральные числа на 5. И выписывать остатки от деления.
1 / 5 = 0 ( остаток 1 )
2 / 5 = 0 ( остаток 2 )
3 / 5 = 0 ( остаток 3 )
4 / 5 = 0 ( остаток 4 )
5 / 5 = 1 ( остаток 0 )
6 / 5 = 1 ( остаток 1 )
7 / 5 = 1 ( остаток 2 )
8 / 5 = 1 ( остаток 3 )
и т.д. То есть остатки всегда лежат в диапазоне [0 .. 4]

Тогда
0 < rand() % (end - start) < (end - start) - 1
И наконец-то
start < start + rand() % (end - start) < start + (end - start) - 1
Упрощая:
start < start + rand() % (end - start) < end - 1
Таким образом метод getRandom() возвращает нам (с некоторыми упрощениями) случайное число в диапазоне [start .. end – 1], где start и end являются входными параметрами метода.

printArray( arr, N );
Вызываем метод печати
Аналогично методу инициализации метод printArray использует такие же входные параметры, такой же цикл FOR, только в нем расположен другой метод:
printf(” %d”, arr[i]);
printf (от англ. print formatted, печать форматированная).
Данный метод принимает строку форматирования и параметры для подстановки в эту строку. В языке Си и Си++ строка форматирования представляет собой строку, завершённую нулевым символом. Все символы, кроме спецификаторов формата, копируются в итоговую строку без изменений. Стандартным признаком начала спецификатора формата является символ % (Знак процента).

Спецификатор формата имеет вид:
%[флаги][ширина][.точность][размер]тип
Обязательными составными частями являются символ начала спецификатора формата (%) и тип.

В данном случае флаг “d” обозначает, что все числа массива являются целыми числами типа int.
После выполнения цикла FOR у нас выведется строка, в которой через пробел будут расположены все элементы нашего массива arr.
После цикла методом printf(“n”); мы просто делаем перенос строки, так как специальный символ “n” обозначает именно перенос строки.

И вот мы добрались до основной логики программы.
Метод void replace(TArray arr, int N)
Он ничего не возвращает, поэтому тип void. Но он изменяет определенные элементы массива. Аналогично предыдущим методам мы пробегаем по массиву с помощью цикла FOR и внутри встречаем условный оператор if.

void replace(TArray arr, int N){
for(int i = 0; i < N; i++){ if(arr[i] % 7 == 0) arr[i] = 0; }
}

Логическое выражение arr[i] % 7 == 0 сравнивает остаток от деления на 7 текущего элемента массива. Если он равен 0, то и элемент мы заменяем на ноль с помощью arr[i] = 0;
Кстати, строку arr[i] = 0; мы вполне можем заменить на arr[i] *= !!(arr[i] % 7);
Или чтобы было более понятно: arr[i] = arr[i] * ( !!(arr[i] % 7) );
Двойное отрицание нужно чтобы преобразовать остаток в логический 0 или логическую 1 в зависимости от кратности семи текущего элемента.

Мы подошли к концу. Данный пример имеет много “дыр” и многие параметры не защищены от ввода “некорректных” данных. Пример максимально упрощен и является “наивным” решением задачи (т.е. первым рабочим решением). Однако этот пример рассмотрен для того, чтобы просто помочь войти вам в программирование на языке Си.
Надеюсь, что начинающим это объяснение поможет.

Черновик для тренировки:

Напишите в комментарии, стоит ли выкладывать такие подробные объяснения некоторых вещей из мира программирования, математики и физики? 😉

5 Comment on “Допустим, перед нами”

  • User
    Nikolay Simakov October 28th, 2018 Reply

    Ждем продолжения изысканий)

  • User
    Алишер Айкын October 28th, 2018 Reply

    Да, было бы отлично, особенно разные алгоритмы

  • User
    Роман Пушкин October 29th, 2018 Reply

    Программа на руби:

    Array.new(4) { rand(1…9) }

    Заходите на огонёк 😉

Add Comment

Your email address will not be published. Required fields are marked *