Лекция 1. Программирование алгоритмов формирования и обработки статических одномерных массивов Часто приходится обрабатывать не одиночные данные, а совокупность данных одного типа. Пусть, например, при табулировании функции необходимо сохранить последовательность значений функции при заданных значениях аргумента для последующей обработки. Если хранить эту последовательность в обычных переменных с уникальными именами, то обращение к каждой переменной последовательности по имени превращается в длинную вереницу однотипных операций с каждой переменной. Программный код становится длинным и плохо обозримым, а программа занимает большой объем памяти. При использовании простых переменных каждой области памяти для хранения данных соответствует свое имя. Если с группой величин одинакового типа требуется выполнять однообразные действия, им дают одно имя, а различают по порядковому номеру, что дает возможность компактно записывать множество операций с помощью циклов. Для этого в алгоритмических языках используются массивы данных. Массив занимает непрерывную область памяти, поэтому обеспечивается быстрый доступ к отдельному элементу массива путем добавления индекса к адресу начала массива в памяти ai i 5 0 4 1 1 2 0 3 22 4 6 5 183 6 5 7 1 8 9 9 Массив, как встроенное средство языка, обладает следующими специфическими особенностями: • это совокупность упорядоченных (расположенных последовательно в памяти) элементов данных; • все элементы массива имеют один и тот же тип данных; • все элементы массива занимают одну непрерывную линейную область памяти; • каждый массив имеет имя. Имя массива – это база (адрес участка памяти, начиная с которого хранятся элементы массива), относительно которой можно обратиться к любому элементу массива. • все элементы массива пронумерованы, начиная с нуля. Доступ к отдельному элементу массива можно осуществлять одинаково эффективно по имени массива и порядковому номеру элемента: с помощью оператора индексирования [] через указатель, посредством операции разыменования * (рассмотрим позже) • можно определить одномерный, двумерный, многомерный массив, указав требуемое количество размерностей. Размер массива ограничен объемом оперативной памяти и типом данных элементов массива. Элементы массива могут иметь любой тип, 1 совпадающий с типом массива. Массив может быть одномерным или многомерным (рис. 1). При этом многомерный массив может иметь не более 32-х размерностей. Количество таких измерений называется рангом массива. Массивы в C++ размещаются в памяти по строкам. Построчный порядок означает, что быстрее всего изменяется последний индекс. Arr[0] Arr[1] Arr[2] Arr[3] Arr[4] Arr[5] Arr[6] а) ArrM[0, 0] ArrM[1, 0] ArrM[2, 0] ArrM[3, 0] ArrM[4, 0] ArrM[0, 1] ArrM[1, 1] ArrM[2, 1] ArrM[3, 1] ArrM[4, 1] ArrM[0, 2] ArrM[1, 2] ArrM[2, 2] ArrM[3, 2] ArrM[4, 2] ArrM[0, 3] ArrM[1, 3] ArrM[2, 3] ArrM[3, 3] ArrM[4, 3] ArrM[0, 4] ArrM[1, 4] ArrM[2, 4] ArrM[3, 4] ArrM[4, 4] ArrM[0, 5] ArrM[1, 5] ArrM[2, 5] ArrM[3, 5] ArrM[4, 5] ArrM[0, 6] ArrM[1, 6] ArrM[2, 6] ArrM[3, 6] ArrM[4, 6] б) Рисунок 1 - Концептуальная раскладка массива: одномерного (а) и многомерного (б) Мы ограничимся рассмотрением одномерных (один индекс) и двумерных (два индекса) массивов. Работа с двумерными массивами будет рассмотрена позже. 1.1. Объявление (определение) массивов. Инициализация массива. Как и любые другие переменные в С++, массив должен быть определен перед его использованием. Объявление массивов осуществляется точно так же, как и переменных. Единственным отличием является необходимость указания рядом с именем массива количества его элементов. Формат объявления одномерного массива имеет следующий вид: тип имяМассива[количество_элементов]; Таким образом, при определении массива обязательно указываются: • имя массива; • тип массива и его элементов; • размер массива (сколько элементов массив может содержать). int age[4];// описание массива из 4 целых чисел На рис. 2 показан синтаксис объявления одномерного массива. Обратите внимание, что, так как нумерация элементов массива начинается с нуля (первый элемент имеет номер 0), а всего в показанном на рисунке массиве age четыре элемента, то последний элемент массива будет иметь номер 3. 2 Рисунок 2 - Синтаксис объявления одномерного массива Размер массива наряду с типом его элементов определяет объем памяти, необходимой для размещения массива. Так как память под массив выделяется при компиляции, то размер массива может быть задан только целой положительной константой или константным выражением. Массивы с константным размером называются статическими. Размер статического массива должен быть задан в программе еще до компиляции и не может быть изменен в ходе выполнения программы. Нельзя задать размерность статического массива с помощью переменной. const int N=5; double arr[N]; //Ok! int N1=5; double ar[N1];//ошибка!Размерность должна быть задана константой Вместе с объявлением массива возможна и его инициализация, при этом значения в списке инициализации указываются через запятую, а сам список заключается в фигурные скобки. тип имяМассива[количество_элементов] = { список_значений }; тип имяМассива[ ] = { список_значений }; Если при объявлении массива не указан его размер, инициализатор должен присутствовать обязательно, в этом случае компилятор выделит память по количеству инициализирующих значений. Например: int coins[6] = {1,5,10,25,50,100};// Для каждого элемента массива // указан инициализатор(начальное значение) int coins[] = {1,5,10,25,50,100}; // То же, размер массива //определяет компилятор На рис. 3 показан синтаксис инициализации одномерного массива. 3 Рисунок 3 - Синтаксис инициализации одномерного массива Инициализирующий список может содержать меньше начальных значений, чем количество элементов в массиве, тогда остальные элементы инициализируются нулями. Если же инициализаторов слишком много, то компилятор выдаст ошибку: int a[10] = {0}; // Инициализация всего массива нулями int b[5] = {3,2,1};// b[0]=3, b[1]=2, b[2]=1, b[3]=0, b[4]=0 int c[4] = {1,2,3,4,5}; // Ошибка! Слишком много инициализаторов Массив можно проинициализировать только при объявлении. Любая попытка с помощью списка инициализаторов присвоить элементам уже существующего массива новые значения вызовет сообщение компилятора об ошибке: int ar[5]; ar = {1,2,3};//Ошибка! Список инициализаторов можно использовать //только при объявлении массива В списке инициализаторов можно использовать переменные: int n1=1,n2=2; int b[4] = {7,n1,n2,8};// b[0]=7, b[1]=1, b[2]=2, b[3]=8 Массив, все элементы которого являются константами, должен быть проинициализирован при объявлении (так же, как и в случае константных переменных базового типа): const int ar[5]; //Ошибка! Требуется список инициализаторов const int ar[] = {1,2,3,4,5};// Корректно, компилятор посчитает //размер массива по списку инициализаторов Важное замечание. Если при инициализации массива не указан его размер, то можно всегда узнать количество элементов массива с помощью оператора sizeof: float arr[]={7,-2.2,100,0,-8.5,1}; int n = sizeof(arr)/sizeof(float); //Количество элементов массива int m = sizeof(arr)/sizeof(arr[0]); //То же самое 4 1.2. Доступ к элементам массива. Формирование и обработка элементов массивов с помощью регулярных циклов с параметром Для доступа к элементу массива после его имени в квадратных скобках указывается номер элемента (индекс). Индекс может быть задан как числом, так и любым выражением целого типа. В С++ при обращении к элементам массива, автоматический контроль выхода индекса за границы массива НЕ производится, что может привести к ошибкам. Напомним, что последний элемент массива имеет номер, на единицу меньший заданного при объявлении размера массива. На рисунке 4 показаны примеры обращения к элементам массива. Рисунок 4 – Обращение к элементам одномерного массива Алгоритмы и программы формирования и обработки одномерных массивов строятся обычно с использованием регулярных циклических структур с параметром и, соответственно, с использованием операторов цикла for. При этом в теле цикла всегда присутствует ссылка на элементы обрабатываемого массива с индексом, совпадающим с параметром цикла или являющимся выражением, включающим этот параметр. Например: int a[9]; //объявление массива for (int i=0;i<9;i++) a[i]=2*i+1; //заполнение массива по формуле В результате работы этого фрагмента программы будет сформирован массив из 9 нечетных чисел: 5 1.3. Использование массивов в качестве параметров функций. Типовые процедуры формирования и вывода массивов. Массивы могут использоваться в качестве параметров функций, причем в функцию всегда передается адрес его первого элемента. При этом информация о количестве элементов массива теряется, и поэтому всегда следует передавать его размер через отдельный параметр. Массивы отличаются от простых переменных тем, что их нельзя передавать по значению. Таким образом, массив всегда передается по адресу, и, следовательно, изменение любого элемента массива в теле функции обязательно повлияет на оригинал. Одномерный массив в качестве формального параметра функции можно определить двумя способами: 1) имя массива с фиксированным размером, например int a[10] 2) имя массива с неопределенным размером, например int a[] Примеры определения функций с параметром-одномерным массивом: void f1(int a[10],int k); void f2(int a[],int k); Вызовы этих функций могут быть, например, следующими: //фрагмент кода вызывающей функции ... const int N=25; int arr[N],mas[N]; // объявление массивов int k=10; ... f1(arr, 5); // вызов функции f1 f2(mas, k); // вызов функции f2 Пример 1.1. Функции формирования массива из случайных чисел. Для отладки программ обработки массивов удобно формировать используемые массивы путем инициализации их элементов случайными числами. В приложениях CLR Windows Forms для формирования массива из случайных чисел используется класс Random из пространства имен System. На рис. 5 приведен пример функции инициализации массива типа int случайными целыми числами из заданного через параметры отрезка. Для получения очередного случайного числа используется метод Next. 6 Рисунок 5 – Программный код функции формирования массива случайными целыми числами из отрезка [a, b]. Вызвать эту функцию из событийной процедуры можно, например, так: input(mas1,10,-10,20); При таком вызове объявленный в событийной процедуре одномерный массив mas1 заполнится 10 случайными числами из диапазона от -10 до 20. Для формирования массива из случайных вещественных чисел используется другой метод класса Random. Метод NextDouble() возвращает случайное число из отрезка [0,1], которое потом следует привести к нужному диапазону [a,b] (рис. 6). Рисунок 6 – Программный код функции формирования массива случайными вещественными числами из отрезка [a, b]. Еще одним способом формирования массивов является инициализация его элементов в соответствии с некоторыми заданными правилами. Пример 1.2. Функция формирования массива из натуральных целых чисел. На рис. 7 приведен пример функции инициализации массива типа int натуральными числами. Рисунок 7 – Программный код функции инициализации массива натуральными числами. 7 Очень удобным способом формирования массива является заполнение его с помощью элемента управления на форме типа ListBox, в коллекцию которого уже записаны нужные значения. Пример 1.3. Написать функцию формирования массива значениями из списка ListBox. На рис. 8 приведен пример процедуры инициализации массива вещественного типа чтением чисел, записанных в элемент управления на форме ListBox. Рисунок 8 – Программный код функции инициализации массива чтением из списка ListBox на форме Обратите внимание, что функция заполнения массива чтением значений из списка ListBox должна обязательно вернуть число элементов в списке, так как именно это число будет количеством элементов в массиве, и оно необходимо для дальнейшей работы с массивом. Функция, представленная на рис. 8, возвращает это число через возвращаемое значение оператором return. Исходные (сформированные) массивы и массивы, полученные в результате обработки исходных массивов, надо уметь отображать на форме программы. Наилучшим образом для отображения массива приспособлен объект типа ListBox. Пример 1.4. Функция вывода элементов одномерного массива в элемент управления ListBox. На рис. 9 приведен пример процедуры вывода одномерного массива в заданный список ListBox. 8 Рисунок 9 – Программный код функции вывода одномерного массива в список ListBox на форме. При необходимости строку для вывода в ListBox надо предварительно привести к нужному формату с помощью метода Format. Перед тем, как приступить к изучению типовых алгоритмов обработки статических одномерных массивов, повторим основные моменты работы с массивами в С++: 1) Размер статического массива может быть только константой или константным выражением. Рекомендуется задавать размер с помощью именованной константы. 2) Элементы массивов нумеруются с нуля, поэтому максимальный номер элемента всегда на единицу меньше размера массива. 3) Автоматический контроль выхода индекса за границы массива не осуществляется, поэтому программист должен следить за этим самостоятельно. 1.4. Типовые алгоритмы формирования и обработки одномерных массивов и примеры их реализации. К типовым алгоритмам формирования и обработки одномерных массивов можно отнести алгоритмы решения следующих задач: • формирование нового массива из элементов одного или нескольких исходных массивов в соответствии с заданными правилами; • вычисление суммы (произведения) значений элементов массива при заданных условиях; • определение максимального (минимального) значений элементов массива и его индекса; • вставка нового элемента в массив по заданному правилу; • удаление элемента из массива по заданному правилу; • обмен значениями между элементами массива по заданному правилу; 9 • сортировка критерию. (упорядочение) элементов массива по заданному Рассмотрим примеры реализации некоторых из этих алгоритмов. Замечание. Практически всегда массивы формируются и обрабатываются с помощью регулярных циклов с параметром. В нижеследующих примерах схем алгоритмов указание в операторе начала цикла 𝑖 = 0, 𝑛 − 1 подразумевает, что n – это количество элементов в массиве. Так как максимальное значение параметра i, если он является индексом элемента массива, на единицу меньше n, то при программировании в условии выполнения цикла удобнее записывать строгое неравенство: i<n. i=0,n-1 for (int i=0; i<n; i++) Пример 1.5. Разработать функцию формирования вещественного массива y из положительных элементов исходного вещественного массива x. Схема алгоритма и программный код функции приведены на рис. 10. Для формирования нового массива из элементов другого массива по какомулибо условию всегда необходим счетчик числа подходящих под условие значений – в данном примере роль такого счетчика, который одновременно является счетчиком элементов нового массива, играет переменная n. Перед началом цикла значение переменной n – количества элементов результирующего массива y – устанавливается в 0, что соответствует пустому массиву y. В регулярном цикле последовательно перебираются все элементы исходного массива x. Если очередной элемент массива x положителен, то он переписывается в новый массив y под номером n. Далее значение n увеличивается на 1, что соответствует увеличению числа элементов нового массива. Вычисленное в результате работы процедуры значение n (число элементов нового массива) возвращается оператором return. Таким образом, если исходный массив не содержит положительных элементов, то функция вернет 0, и вызывающая программа должна соответствующим образом обработать такой случай. 10 Рисунок 10 – Схема алгоритма и программный код функции создания нового одномерного массива из положительных элементов исходного массива Пример 1.6. Разработать функцию вычисления произведения ненулевых элементов заданного вещественного массива. Схема алгоритма и программный код функции приведены на рис. 11. Входными параметрами функции являются исходный массив a и число его элементов k, возвращаемый результат накапливается в переменной z. Перед началом цикла значение этой переменной устанавливается в 1. В цикле с параметром i последовательно перебираются все элементы входного массива. Если очередной элемент массива не равен 0, то он домножает произведение z. По окончании цикла в переменной z оказывается искомое произведение, которое возвращается оператором return. 11 Рисунок 11 – Схема алгоритма и программный код функции, вычисляющей произведение ненулевых элементов массива. Пример 1.7. Разработать функцию, которая находит наименьший элемент вещественного массива и его индекс. Схема алгоритма и программный код функции приведены на рис. 12. Входными параметрами функции являются исходный массив x и число его элементов n. Перед началом цикла значение переменной xmin устанавливается равным значению первого элемента массива (с индексом 0), а значение переменной k – соответственно равным 0. В цикле с параметром i последовательно перебираются остальные элементы входного массива, начиная со второго (с индексом 1). Если очередной элемент меньше текущего наименьшего xmin, то он заменяет xmin, а в переменной k фиксируется его индекс. В противном случае xmin и k сохраняют свои прежние значения, и анализируется следующий элемент массива. По окончании цикла в переменных xmin и k оказываются искомые значения, которые возвращаются через возвращаемое значение и через параметр по ссылке. Рисунок 12 – Схема алгоритма и программный код функции нахождения наименьшего элемента массива и его номера Пример 1.8. Разработать функцию, которая в заданном вещественном массиве переставляет элементы с целыми значениями в начало массива. Схема алгоритма и программный код функции приведены на рисунке 13. Входным и одновременно выходным параметром процедуры является массив c. Вторым входным параметром является n - размер массива c. 12 Для того чтобы переставить целые элементы в начало массива, в переменной k будем хранить текущий номер элемента, в который переписывается очередное целое значение. Перед началом цикла значение этой переменной устанавливается в 0. Чтобы определить, является ли очередной элемент массива целым числом, проводится его сравнение с его же целой частью. Целая часть значения c[i] выделяется с помощью функции floor(). Если очередной элемент массива c[i] содержит целое значение, то производится обмен значений двух элементов массива c[k] и c[i] c использованием вспомогательной переменной temp. После этого k увеличивается на 1 для подготовки следующей перестановки. Рис. 13 – Схема алгоритма и программный код функции перестановки элементов с целыми значениями в начало массива 13