Программирование на Lazarus

Массивы простые, многомерные и динамические

Показывать лекцию целиком

Цель лекции

Изучение простых, многомерных и динамических массивов.

Массив

В практике программирования нередко возникает необходимость обработать большое количество однотипных данных. Допустим, распределить недельную прибыль кафе по дням. Что для этого нужно? Использовать переменные типа currency, как раз для таких случаев и применяемые. И сколько переменных нам нужно? Ну, ясен вопрос - по переменной на день, всего семь штук. Давайте посмотрим, как такую задачу можно было бы решить на Lazarus (пример академический, выполнять его не нужно):

var
   day1, day2, day3, day4, day5, day6, day7: currency;
    

Довольно длинное объявление получилось, правда? А если бы нужно было посчитать не недельную прибыль, а месячную? Или годовую? Теперь предположим, что нам нужно посчитать сумму этой прибыли. Это выглядело бы примерно так:

pribil:= day1 + day2 + day3 + day4 + day5 + day6 + day7;
    

А если бы пришлось считать месячную или годовую прибыль? Какой длины оператор тогда бы получился? А ведь часто бывают и более сложные расчеты, которые требуется провести с каждой однотипной переменной.

К счастью, в языках программирования существует такой инструмент, как массивы.

Массив - это формальное объединение нескольких однотипных переменных в единый набор, с одним общим именем.

Фактически, получается цепочка однотипных переменных под общим именем, где у каждой такой переменной имеется собственный номер в ячейке - индекс. Такие переменные называются элементами массива, каждый элемент имеет свой номер. Объявляется массив в разделе переменных следующим образом:

var
   a: array[1..7] of currency;
    

Ключевое слово array указывает, что это будет массив. Числа в квадратных скобках разделяются двумя точками и обозначают диапазон индексов. Так, [1..7] означает, что индексы массива будут от 1 до 7, то есть, 7 штук. После ключевого слова of указывается тип массива, такой же тип будет и у каждого элемента массива. В результате, компилятор создает в оперативной памяти семь ячеек по 8 байт каждая (тип currency занимает 8 байт):

(рис 13.1) Размещение элементов массива a в памяти

Тип данных, используемый в массивах, так же может быть любым. Во многих других языках программирования диапазон индексов всегда начинается с 0. В языках, основанных на Паскале, диапазон может начинаться с любой цифры: с 0, 1, или 438, если вам так нравится. Хотя программисты обычно начинают диапазон либо с 0, либо с 1, в зависимости от обстоятельств. Пример объявления различных массивов:

var
   name: array[0..30] of string;
   year: array[1..2013] of integer;
   man: array[10..25] of boolean'
    

Хорошо, массивы мы объявили. Что с ними делать дальше? Мы можем обратиться к любому элементу массива, указав после имени в квадратных скобках индекс этого элемента. Так, мы можем присвоить значение элементу или получить его значение. Другими словами, мы можем обращаться с элементами массива, как с отдельными переменными. Примеры:

…
a[1]:= 3520.45;
a[2]:= a[1] + 12.4;
ShowMessage('Прибыль за пятницу составила  ' + FloatToStr(a[5]));
…
    

Ну, ладно, мы научились создавать и использовать массивы. Но какие же преимущества у массивов в отличие от простых однотипных переменных? О первом таком преимуществе мы уже говорили, вот как просто можно объявить массив с элементами, рассчитанными на год:

var
   a: array[1..365] of currency;
    

Попробуйте-ка объявить 365 отдельных переменных! Второе преимущество гораздо важней: обрабатывать элементы массива намного проще, чем отдельные переменные. Например, нам нужно создать программу, которая бы могла сделать перевод температур из привычных нам Цельсиев в непривычные американские Фаренгейты. И пусть диапазон возможных температур будет от -100 градусов до +200. Идея такова: пользователь вводит нужную температуру, а программа выводит результат на экран. Конечно, проще было бы делать нужный расчет в момент, когда пользователь введет свои данные, и нажмет кнопку "рассчитать". Но мы поступим по-другому: мы сначала сделаем все расчеты и поместим результаты в массив, а затем будем просто выводить нужные данные. Массив сделаем глобальным, чтобы заполнять его в одном событии, а выводить данные в другом. Открываем Lazarus с новым проектом, сохраняем его сразу в папку 13-01 там, где у нас хранятся все учебные проекты. Проект сохраним под именем MyTemp, а модуль главной формы, как обычно - Main. Займемся главной формой, измените у нее следующие параметры:

  • Name = fMain
  • Caption = Перевод температурных шкал
  • BorderStyle = bsDialog
  • Position = poMainFormCenter
  • Далее, перейдите на вкладку Additional Палитры компонентов, найдите и установите на форму компонент TStaticText, который предназначен для вывода на экран пояснительного текста, в том числе и многострочного. Если вы забыли, то найти нужный компонент просто: нужно просто подвести указатель мыши к компоненту, и через короткое время появится всплывающая подсказка с именем компонента.

    В принципе, для этих целей можно использовать и простую метку TLabel, но было бы интересней познакомиться и с другими компонентами. В Инспекторе объектов в свойстве Caption нажмите на кнопку "" справа от свойства:

    (рис 13.2) Свойство Caption компонента TStaticText

    В открывшемся редакторе напишите следующий текст:

    Укажите температуру в Цельсиях 
    от -100 до 200 градусов:
        

    Именно так, в две строки. Затем измените размеры компонента на форме, чтобы текст отображался точно в таком виде, как мы ввели. И еще: в свойстве Alignment выберите taRightJustify, чтобы текст выравнивался по правому краю.

    Теперь перейдите на вкладку Misc палитры компонентов, найдите там и установите на форму, правее TStaticText, компонент TSpinEdit, который предназначен для ввода пользователем целых чисел. Этот компонент похож на простой TEdit, но справа от него есть стрелки вверх и вниз, которыми пользователь может прибавлять или убавлять указанное в строке число. Использование этого компонента гарантирует нам, что пользователь не сможет ввести в него ничего, кроме целого числа. А чтобы еще гарантировать правильный диапазон этих чисел, в свойстве MaxValue (максимальное возможное значение) установите 200, а в MinValue (минимальное возможное значение) установите -100 (минус сто). Теперь пользователь не сможет ввести в это поле ничего, кроме целых чисел. Если же он попытается превысить максимальное значение, то компонент автоматически установит число 200 - максимально возможное. И наоборот, если указать число меньше, чем -100, компонент установит -100, так что нам не нужно беспокоиться об ошибках. Кроме того, в этом компоненте позже нам понадобятся ещё два свойства: Text (установленное значение в виде строки), и Value (установленное значение в виде целого числа).

    По умолчанию, компонент недостаточно широк, поэтому установите в его свойстве Width значение 75. И еще: нам придется обращаться к компоненту по имени, а SpinEdit1 - это слишком длинное имя. Измените его свойство Name на SE1.

    Ну и наконец, установите на форму простую кнопку, в свойстве Caption которой напишите Рассчитать температуру. Надпись на кнопке не уместится, поэтому кнопку придется удлинить. Расположите компоненты на форме примерно так:

    (рис 13.3) Внешний вид формы

    Теперь займемся предварительными расчетами. Для начала нам нужно сделать глобальный массив вещественных чисел - температура по Фаренгейту уже не будет целым числом. Значение по Фаренгейту вычисляются так: берется температура по Цельсию, умножается на 9/5, затем к результату прибавляется 32. Поэтому в глобальном разделе var, сразу над ключевым словом implementation объявляем массив atemp:

    var
      fMain: TfMain;
      atemp: array[-100..200] of real;  //наш глобальный массив
    
    implementation
        

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

    Выделите форму, щелкнув по её свободному месту, затем в Инспекторе объектов перейдите на вкладку События. Нам нужно событие OnCreate - оно возникает только однажды, при создании формы. Щелкните дважды по этому событию, чтобы сгенерировать его. Код события следующий:

    procedure TfMain.FormCreate(Sender: TObject);
    var
      i: smallint;  //счетчик для цикла
    begin
      for i:= -100 to 200 do
        atemp[i]:= i * 9/5 + 32;
    end;
        

    Вот теперь, наконец, мы сможем оценить всю прелесть использования массивов! Давайте разберем этот код. В разделе var мы объявили переменную i типа smallint. Это - счетчик для цикла for, который мы использовали в коде. Так как диапазон нужных нам значений располагается от -100 до +200, тип целых чисел smallint - самый подходящий. А теперь обратите внимание на код:

      for i:= -100 to 200 do
        atemp[i]:= i * 9/5 + 32;
        

    Это - самая главная "фишка" массивов - обработка всего массива, каким бы большим он не был, в простом цикле. Вначале счетчик i принимает значение -100. Внутри цикла мы присваиваем значение одному элементу массива. Если мы заменим переменную i на её значение, как это делает компилятор в процессе работы, то получим следующее:

        atemp[-100]:= -100 * 9/5 + 32;
        

    Таким образом, мы высчитываем перевод температуры из Цельсия в Фаренгейты для конкретной температуры: -100 градусов по Цельсию, и полученный результат присваиваем элементу массива atemp[-100]. Затем цикл повторяется, i увеличивается на единицу, и уже равна -99. Новое значение присваивается новому элементу массива, и так до конца цикла. Как вы думаете, сколько элементов массива будет заполнено в этом цикле? Кто сказал 300?! А про ноль вы забыли? 301 раз будет работать цикл, и 301 различное значение будет присвоено 301 элементу массива. А теперь представьте, как это выглядело бы для отдельных переменных:

    t_100:= -100 * 9/5 + 32;
    t_99:= -99 * 9/5 + 32;
    …
    t200:= 200 * 9/5 + 32;
        

    Вместо двух строчек цикла мы получили бы 301 строку кода с одинаковыми вычислениями!

    Но вернемся к нашему проекту, ведь нам еще нужно вывести на экран результат перевода температур. Сгенерируем для кнопки Button1 событие OnClick. Её код будет совсем простой:

    procedure TfMain.Button1Click(Sender: TObject);
    begin
      ShowMessage(SE1.Text + ' Цельсия = ' +
                             FloatToStr(atemp[SE1.Value]) + ' Фаренгейта');
    end;
        

    Здесь у нас всего один оператор - процедура ShowMessage(). Она формирует выводимое сообщение из 4 частей: SE1.Text - это то значение, которое установил пользователь в компоненте TSpinEdit в виде строки. Затем мы вставляем в сообщение текст ' Цельсия = '. Обратите внимание на пробелы вначале и в конце этого кусочка - без них слова слипались бы, а это некрасиво. Далее, мы добавляем к строке следующий код:

    FloatToStr(atemp[SE1.Value])
        

    Здесь SE1.Value - это то значение, которое установил пользователь, но уже в виде целого числа! Допустим, пользователь установил значение 35, тогда этот код будет таким:

    FloatToStr(atemp[35])
        

    В элементе массива atemp[35] у нас уже хранится переведенное в Фаренгейты значение температуры 35 по Цельсию. А функция FloatToStr() только переведет это значение в строку. Таким образом, для значения 35 сформируется сообщение: "35 Цельсия = 95 Фаренгейта". Сохраните проект, скомпилируйте его и убедитесь, что программа корректно работает в указанных пределах температур, а превысить их пользователь не сможет, если и захочет, как не сможет ввести ничего другого, кроме целого числа.

    Многомерный массив

    Массивы, которые мы до сих пор разбирали, были одномерными - визуально их можно представить, как цепочку ячеек или одну строку таблицы. Однако бывают ситуации, когда этого недостаточно. Допустим, нужно сохранить какую-то таблицу. Одномерный массив с такой задачей не справится. Для этого существуют многомерные массивы, которые имеют размерность от двух и больше. Например, объявить двухмерные массивы из 5 строк и 10 колонок можно следующим образом:

    var
       a: array[1..5, 1..10] of string;
       b: array[0..4, 0..9] of integer;
        

    То есть, в квадратных скобках через запятую указывается диапазон вначале строк, а затем колонок. Визуально, такой массив можно было бы представить так:

    Представление двухмерного массива a
    1,1 1,2 1,3 1,4 1,5 1,6 1,7 1,8 1,9 1,10
    2,1 2,2 2,3 2,4 2,5 2,6 2,7 2,8 2,9 2,10
    3,1 3,2 3,3 3,4 3,5 3,6 3,7 3,8 3,9 3,10
    4,1 4,2 4,3 4,4 4,5 4,6 4,7 4,8 4,9 4,10
    5,1 5,2 5,3 5,4 5,5 5,6 5,7 5,8 5,9 5,10

    Обратиться к отдельному элементу массива можно двумя способами:

    a[2, 9]:= 'Строка 1';  //1-й способ
    a[3][10]:= 'Строка 2';  //2-й способ
        

    В первом случае мы обращаемся к элементу во второй строке, девятой колонке. Во втором - к третьей строке, десятой колонке. Первый способ обращения к элементу кажется компактней, его мы и будем использовать впредь.

    Доработаем наш проект. Что, если мы будем конвертировать нашу температуру не только в Фаренгейты, но и в Кельвины (единица термодинамической температуры в СИ - Международной Системе Единиц)? Пересчет Цельсия в Кельвины еще проще, чем в Фаренгейты: нужно к температуре по Цельсию прибавить 273,15. Для этого нам потребуется вместо одномерного массива объявить двухмерный. Измените код над служебным словом implementation:

    atemp: array[1..2, -100..200] of real;
        

    В первой строке у нас будут Фаренгейты, во второй - Кельвины. А колонки мы отведем под требуемый диапазон. Далее, нам потребуется изменить цикл в событии OnCreate формы:

    procedure TfMain.FormCreate(Sender: TObject);
    var
      i: smallint;
    begin
      for i:= -100 to 200 do begin
        atemp[1, i]:= i * 9/5 + 32;  //Фаренгейты
        atemp[2, i]:= i + 273.15;  //Кельвины
      end;
    end;
        

    Обратите внимание: поскольку нам в цикле требуется выполнить не один оператор, а два, нам пришлось заключить их в программные скобки begin…end, сделав составной оператор. Далее, все просто: для строки 1 нашего массива мы рассчитываем Фаренгейты, как в прошлый раз. А для строки 2 уже Кельвины.

    Но нам потребуется переделать и событие кнопки OnClick:

    procedure TfMain.Button1Click(Sender: TObject);
    begin
      ShowMessage(SE1.Text + ' Цельсия будет равно:' + #13 +
                  FloatToStr(atemp[1, SE1.Value]) + ' Фаренгейта' + #13 +
                  FloatToStr(atemp[2, SE1.Value]) + ' Кельвина');
    end;
        

    С кодом, надеюсь, вы разберетесь самостоятельно? Итак, если пользователь введет, например, 36 градусов Цельсия, то нажав на кнопку, получит следующее сообщение:

    (рис 13.4) Сгенерированное программой сообщение

    Но размерность массива может быть и больше. Трехмерный массив визуально можно представить следующим образом:

    (рис 13.5) Визуальное представление трехмерного массива

    Объявить подобный массив можно было бы так:

    var
       a: array[1..5, 1..5, 1..5] of integer;
        

    А обращаться к отдельным элементам так:

    a[2, 3, 4]:= 12;
        

    Как бы выглядел четырехмерный массив визуально вообще невозможно представить. У вас есть возможность работать хоть с десятимерным массивом, однако в практике программирования обычно используют одно- и двухмерные массивы, и очень редко возникает надобность в трехмерном. Не забывайте про один из основных принципов программирования, который сами программисты называют KISS (Keep It Simple, Stupid - будь проще, дурачок). Этот принцип подразумевает, что код программы не стоит усложнять без нужды - сложный код тяжелее воспринимается самим программистом, больше нагружает процессор, а ошибки времени выполнения (run time errors - ошибки, возникающие во время выполнения программы, обычно это логические ошибки) в таком коде сложнее отслеживать.

    Динамический массив

    В практике программирования иной раз случается работать с массивами, размерность которых заранее неизвестна - она может зависеть от данных, введенных пользователем, от текущих параметров, от объема обрабатываемой информации, да еще много от чего. Программист в этих случаях не может указать конкретные границы индексов массива. В таких случаях используют динамические массивы.

    Динамическими называются массивы, при объявлении которых размер не указывается. А во время выполнения программы размер такого массива можно изменять.

    Объявляются динамические массивы также в разделе var, следующим образом:

    var
       da: array of integer;
        

    Как видите, мы указываем только тип массива, но не его размерность. Память под объявленный массив при этом не отводится. В дальнейшем с динамическим массивом мы можем производить различные операции: устанавливать размерность (длину) массива, увеличивать или уменьшать эту размерность (потому массив и динамический), узнавать высшие и низшие границы диапазона индексов массива, присваивать значения отдельным элементам или наоборот, считывать эти значения. Познакомьтесь с функциями, которые все это проделывают.

    SetLength() - устанавливает размер массива. Синтаксис:

    SetLength(<массив>, <длина>);
        

    Пример:

    SetLength(da, 5);  //установили размер массива в 5 элементов
        

    Тут следует оговориться, что в отличие от обычного массива, начальный индекс которого может быть любым, индексация динамического массива всегда начинается с нуля. То есть, индексация элементов массива в нашем примере будет от 0 до 4 - всего пять элементов. Как только мы установили размер массива, в памяти выделяется место под него. В нашем примере будет отведено по 4 байта (тип integer) для 5 элементов массива. Обращаться к этим элементам можно обычным образом:

    da[0]:= 5;
    da[4]:= da[0];
        

    Теперь мы можем прибавить элемент к массиву, установив для него новый размер:

    SetLength(da, 6);
        

    В этом случае, один пустой элемент будет добавлен в конец массива. Если же напротив, мы уменьшим его размер:

    SetLength(da, 4);
        

    то массив усекается. Если последний элемент содержал какие-то данные, они будут потеряны. Освободить память массива можно, присвоив ему нулевую размерность:

    SetLength(da, 0);
        

    Также освободить память можно, присвоив массиву значение nil (ничего, пусто):

    da:= nil;
        

    Впрочем, этого можно и не делать: по окончании процедуры, где данный массив был объявлен, память будет освобождена автоматически.

    Динамические массивы могут быть и многомерными. Например, двухмерный массив целых чисел можно объявить так:

    var
      a: array of array of integer;
        

    Затем такому массиву можно присвоить размерность, например, 4 на 5:

    SetLength(a, 4, 5);
        

    Это будет, как если бы мы объявили простой массив:

    var
      a2: array[0..3, 0..4];
        

    Length() - возвращает размер динамического массива, то есть, количество его элементов. Например, нам нужно посмотреть размер массива, и если он пустой, то добавить в него один элемент:

    if Length(da) = 0 then SetLength(da, 1);
        

    Low() - возвращает нижний индекс массива, у динамических массивов это всегда ноль.

    High() - возвращает верхний индекс массива, но это не то же самое, что количество элементов. Если у массива 5 элементов, то Length() вернет 5, а High() вернет 4, так как индексация начинается с нуля.

    Пример обхода массива от первого до последнего элемента:

    for i:= Low(da) to High(da) do
       da[i]:= i * i;
        

    В примере мы каждому элементу присваиваем квадрат его индекса. Давайте поработаем с динамическими массивами на практике. Чтобы не портить наш конвертер температур, закройте его и откройте новый проект. Мудрить мы не будем - нам нужна только форма и простая кнопка на ней. Переименовывать тоже ничего не будем, это же просто пример. Сохраните новый проект в папку 13-02.

    Сгенерируйте процедуру OnClick для кнопки, её код будет таким:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      s: string;  //для запроса
      da: array of string;  //динамический массив строк
      i: integer;  //счетчик
    begin
      //узнаем у пользователя, сколько строк делать:
      s:= '0';
      InputQuery('Привет!', 'Сколько строк желаете создать?', s);
      //если ноль, то ничего не делаем, выходим из процедуры:
      if StrToInt(s) = 0 then Exit;
    
      //иначе устанавливаем указанную размерность массива:
      SetLength(da, StrToInt(s));
      //теперь обойдем весь массив, устанавливая в его элементы значения:
      for i:= Low(da) to High(da) do
        da[i]:= 'Строка №' + IntToStr(i + 1);
    
      //теперь снова обойдем массив и создадим сообщение из его строк:
      s:= '';
      for i:= Low(da) to High(da) do
        s:= s + da[i] + #13;
      ShowMessage(s);
    
    end;
        

    Код содержит достаточно подробные комментарии. Вначале в строковую переменную s мы получаем количество желаемых строк. Так как функция InputQuery() возвращает только строку, нам придется получать целое число в виде строки - никакой проверки мы здесь не делаем, это же только демонстрация работы с динамическим массивом. Поэтому правильность вводимого числа оставим на совести пользователя.

    Далее, мы проверяем - не ноль ли это? Если строк создавать не нужно, мы просто выходим из процедуры, пропуская весь остальной код. Если же пользователь ввел какую-то цифру, причем она должна быть больше нуля, то мы устанавливаем указанную размерность массива:

    SetLength(da, StrToInt(s));
        

    Далее, с помощью цикла for мы обходим весь массив от первого до последнего элемента. В каждый элемент мы записываем строку: "Строка №", добавляем номер элемента и переход на новую строку. Обратите внимание, для первой строки i будет равно 0, поэтому мы указываем i + 1:

      for i:= Low(da) to High(da) do
        da[i]:= 'Строка №' + IntToStr(i + 1);
        

    Затем мы очищаем строковую переменную s, чтобы вторично воспользоваться ей, собрать в неё сообщение. Что и делаем в следующем цикле:

      for i:= Low(da) to High(da) do
        s:= s + da[i] + #13;
        

    Получается, в процедуре мы дважды обходили массив: сначала чтобы занести в него значения, затем чтобы считать их. Теперь сохраните проект, скомпилируйте его и запустите на выполнение. Имейте в виду, если вы укажете слишком большое число, то окно сообщения не уместится в экране - в этом случае закрыть его можно будет стандартными клавишами <Alt + F4>.

    Вернуться к учебному плану