Pascal: занятие № 5. одномерные массивы в паскале

Передача массива в функцию

Обработку массивов удобно организовывать с помощью специальных функций. Для обработки массива в качестве аргументов функции необходимо передать

  • адрес массива,
  • размер массива.

 
Исключение составляют функции обработки строк, в которые достаточно передать только адрес.
При передаче переменные в качестве аргументов функции данные передаются как копии. Это означает, что если внутри функции произойдет изменение значения параметра, то это никак не повлияет на его значение внутри вызывающей функции.
Если в функцию передается адрес переменной (или адрес массива), то все операции, выполняемые в функции с данными, находящимися в пределах видимости указанного адреса, производятся над оригиналом данных, поэтому исходный массив (или значение переменной) может быть изменено вызываемой функцией.Пример на Си Дан массив из 10 элементов. Поменять местами наибольший и начальный элементы массива. Для операций поиска максимального элемента и обмена использовать функцию.

123456789101112131415161718192021222324252627282930313233343536373839404142

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>// Функция обменаvoid change(int *x, int n){  // x — указатель на массив (адрес массива)  // n — размер массива  int i;  int max, index;  max = x;  index = 0;  // Поиск максимального элемента  for (i = 1; i<n; i++)  {    if (x>max)    {      max = x;      index = i;    }  }  // Обмен  x = x;  x = max;}// Главная функцияint main(){  int a;  int i;  for (i = 0; i<10; i++)  {    printf(«a = «, i);    scanf(«%d», &a);  }  change(a, 10);  // вызов функции обмена          // Вывод элементов массива  for (i = 0; i<10; i++)    printf(«%d «, a);  getchar();  getchar();  return 0;}

Результат выполненияПример на Си Дан массив размерности n. Вычислить произведение четных элементов

12345678910111213141516171819202122232425262728293031

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>// Функция вычисления произведения чётных элементовint func(int *x, int n)  // произведение четных элементов{  int p = 1;  // начальное значение произведения  int i;  for (i = 0; i<n; i++)   {    if (x % 2 == 0)  // остаток от деления на 2 равен 0?      p = p * x;  }  return p;}// Главная функцияint main() {  int a; // объявлен массив a из 5 элементов  int i;  int pr;  // Ввод элементов массива  for (i = 0; i<5; i++)   {    printf(«a = «, i);    scanf(«%d», &a); // &a — адрес i-го элемента массива  }  pr = func(a, 5); // вычисление произведения  printf(«\n pr = %d», pr); // вывод произведения четных элементов  getchar();   getchar();  return 0;}

Результат выполнения

Язык Си

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

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

Общий синтаксис для объявления многомерного массива такой:

Пример с массивом трех измерений:

Если думать о трехмерном массиве как об одномерном, содержащем несколько двумерных, то его можно изобразить как список таблиц. Даже для массива threeArr такой вид будет довольно громоздким – 3 таблицы по 4 строки и 5 столбцов. Для примера представим только вторую таблицу (когда первый индекс равен 1, то есть threeArr):

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

Ячейка с координатами выделена подчеркиванием, ее значение присвоено переменной t124.

Использование количества элементов многомерного массива

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

  • Length: выдает количество ячеек во всем массиве (как если бы перемножить размеры его измерений). Возвращает int, и при работе с большими массивами может возникнуть ошибка, если общее число элементов превысит максимально допустимое значение типа int.
  • Для того чтобы узнать длину измерения многомерного массива используется GetLength с указанным номером измерения в скобках. Индексация измерений ведется с нуля, потому GetLength(0) выдаст длину первого измерения.
  • Рангом массива называют его мерность. Ранг хранится в свойстве Rank.

Пример:

6.2. Управление жизненным циклом динамического массива

Стандартный интеллектуальный указатель можно использовать для управления жизненным циклом динамического массива (см. ). Он имеет частичную специализацию для массивов, которая перегружает оператор вместо оператора и использует оператор в качестве удалителя по умолчанию. Вот пример:

Эта поддержка не является полноценной: не хранится информация о размере массива, соответственно, не поддерживается интерфейс стандартных контейнеров и диапазонный . Такое использование не рекомендуется, вместо этого лучше использовать . Интеллектуальный указатель не имеет даже такой поддержки массивов и совсем не рекомендуется для работы с динамическими массивами.

Инициализация многомерных массивов

Значения элементов многомерного массива, как и в одномерном случае, могут быть заданы константными значениями при объявлении, заключенными в фигурные скобки {}. Однако в этом случае указание количества элементов в строках и столбцах должно быть обязательно указано в квадратных скобках [].Пример на Си

123456789

#include <stdio.h>int main(){  int a = { 1, 2, 3, 4, 5, 6 };  printf(«%d %d %d\n», a, a, a);  printf(«%d %d %d\n», a, a, a);  getchar();  return 0;}

Результат выполнения

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

123456789101112131415161718192021222324252627

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main(){  int a; // массив из 2 строк и 3 столбцов  int i, j;  // Ввод элементов массива  for (i = 0; i<2; i++)  // цикл по строкам  {    for (j = 0; j<3; j++) // цикл по столбцам    {      printf(«a = «, i, j);      scanf(«%d», &a);    }  }  // Вывод элементов массива  for (i = 0; i<2; i++)  // цикл по строкам  {    for (j = 0; j<3; j++) // цикл по столбцам    {      printf(«%d «, a);    }    printf(«\n»); // перевод на новую строку  }  getchar(); getchar();  return 0;}

Результат выполнения

Классика жанра

Если мы откроем классический труд «Язык программирования C» Брайана Кернигана и Денниса Ритчи, то прочитаем, что «В языке C есть возможность работать с многомерными прямоугольными массивами, хотя на практике они используются гораздо реже, чем массивы указателей». C++ практически полностью унаследовал работу с многомерными массивами своего предтечи.

Определение автоматических многомерных массивов

В этом разделе я буду иногда употреблять термин «матрица» как синоним термина «двумерный массив». В C/C++ прямоугольный двумерный массив чисел действительно реализует математическое понятие «матрица». Однако, в общем случае, двумерный массив — понятие гораздо более широкое, чем матрица, поскольку он может быть и не прямоугольным, и не числовым.

Определение автоматических многомерных массивов почти полностью совпадает с определением одномерных массивов (о чём было рассказано в первой статье), за исключением того, что вместо одного размера может быть указано несколько:

В этом примере определяется двумерный массив из 3 строк по 5 значений типа в каждой строке. Итого 15 значений типа .

Во втором примере определяется трёхмерный массив, содержащий 3 матрицы, каждая из которых состоит из 5 строк по 2 значения типа в каждой строке.

Понятно, что тип данных, содержащихся в многомерном массиве, может быть любым.

При дальнейшем изложении для таких многомерных массивов будет употребляться термин «C-массив», что бы отличать их от массивов других видов.

Инициализация

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

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

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

Заполнение массива значениями

Многомерный массив заполняется значениями с помощью вложенных циклов. Причём, как правило, количество циклов совпадает с размерностью массива:

В этом примере каждому элементу массива присваивается значение, первая цифра которого указывает номер строки, а вторая цифра — номер столбца для этого значения (нумерация с 1).

Вывод значений массива на консоль

В продолжение предыдущего примера можно написать:

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

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

Здесь присваивание значения элементу массива и вывод на консоль происходят в одной группе циклов.

Расположение в памяти

Для многомерного C-массива выделяется единый блок памяти необходимого размера: .

Значения располагаются последовательно. Самый левый индекс изменяется медленнее всего. Т.е. для трёхмерного массива сначала располагаются значения для первой (индекс 0) матрицы, затем для второй и т.д. Значения для матриц располагаются построчно (ср. со статической инициализацией массива выше).

Имя (идентификатор) многомерного C-массива является указателем на первый элемент массива (так же как и для одномерных массивов)

Если код из последнего примера немного изменить:

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

№1

Поскольку все значения многомерного C-массива располагаются последовательно, то, пользуясь адресной арифметикой, можно сделать следующий хак:

Или даже так:

В последнем фрагменте осуществляется доступ к значениям двумерного массива как к одномерному массиву. Цивилизованное решение реализуется через .

№2

Из двух примеров, приведённых выше, следует, что работу с двумерным или многомерным массивом (в понимании на более высоком уровне абстракции) технически можно организовать посредством одномерного массива соответствующего размера:

Этот приём достаточно распространён. Его выгода в том, что массив не обязательно должен быть выделен автоматически. Его можно выделять и динамически. Но при этом логически рассматривать как C-массив.

Вышеприведённый код написан в духе чистого C. В C++ обычно такие вещи прячут в класс, оставляя снаружи лаконичный интерфейс без всяких следов адресной арифметики.

Переполнение массива

Пускай у вас есть такой код

int A;
int i;

for (i=0; i<=10; i++) {
	A = 1;
}

Здесь цикл for задан с ошибкой. В некоторых старых версиях компиляторов этот код зацикливался. Дело в том, что переменная i располагалась при
компиляции сразу за массивом A. При выходе за границы массива счётчик переводился в 1.
Массивы небезопасны, так как неправильная работа с индексом может приводить к доступу к произвольному участку памяти (Теоретически. Современные компиляторы сами заботятся о том, чтобы вы не копались в чужой памяти).

Если вы работаете с массивами, то необходимо следить за тем, чтобы счётчик не превышал размер массива и не был отрицательным. Для этого, как минимум,

  • 1. Используйте тип size_t для индексирования. Он обезопасит вас от отрицательных значений и его всегда хватит для массива любого размера.
  • 2. Помните, что массив начинается с нуля.
  • 3. Последний элемент массива имеет индекс (размер массива — 1)

3.2. Синтаксис инициализации массивов

3.2.1. Общие положения

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

Со времен C массивы можно было инициализировать с помощью синтаксиса агрегатной инициализации:

В С++11 появилась универсальная инициализация (uniform initialization) и теперь можно инициализировать так:

Для универсальной инициализации также можно использовать =, и различать эти два типа инициализации не всегда просто, а, скорее всего, не очень нужно.

Размер массива можно не указывать, тогда он определится по числу инициализаторов.

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

Массивы констант тривиального типа требуют обязательного списка инициализации.

Число инициализаторов может быть меньше размера массива, в этом случае оставшиеся элементы инициализируются конструктором по умолчанию.

Символьные массивы можно инициализировать строковым литералом.

Размер такого массива будет на единицу больше числа символов строки, нужно хранить завершающий нулевой символ.

3.2.2. Инициализация членов класса

В С++11 появилась возможность инициализировать массивы, являющиеся нестатическими членами класса. Это можно сделать двумя способами: непосредственно при объявлении или в списке инициализации членов при определении конструктора.

Правда в этом случае надо всегда явно задавать размер массива, неявное определение размера через список инициализации не разрешается.

Статические массивы, как и ранее, можно инициализировать только при определении, размер массива может быть определен через список инициализации.

3.2.3. Требования к инициализаторам

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

или

Наличие нужного преобразования эквивалентно корректности инструкции

Элемент списка инициализации может быть сам списком инициализации. В этом случае корректность этой инструкции также гарантирует корректную инициализацию элемента массива.

Рассмотрим пример.

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

Этот пример также демонстрирует как с помощью списка инициализации мы можем создать массив для типа у которого нет конструктора по умолчанию. Но в этом случае число инициализаторов должно совпадать с размером массива.

Общее описание

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

Количество используемых индексов массива может быть различным: массивы с одним индексом называют одномерными, с двумя — двумерными, и т. д. Одномерный массив — нестрого соответствует вектору в математике; двумерный («строка», «столбец»)— матрице. Чаще всего применяются массивы с одним или двумя индексами; реже — с тремя; ещё большее количество индексов — встречается крайне редко.

Пример фиксированного массива на языке Паскаль
    {Одномерный массив целых чисел.
     Нумерация элементов от 1 до 15} 
  a array 1..15 of Integer;
    {Двумерный массив символов.
     Нумерация по столбцам по типу Byte (от 0 до 255)
     по строкам от 1 до 5}
  multiArray  array Byte, 1..5 of Char; 
    {Одномерный массив из строк.
     Нумерация по типу word (от 0 до 65536)}
  rangeArray  array Word of String;
Пример фиксированного массива на С/С++
  int Array10];         // Одномерный массив: целых чисел, размера 10;
                         // Нумерация элементов — от 0 до 9.
                         
  double Array12];  // Двумерный массив: 
                         // вещественных чисел двойной точности, 
                         // размера 12 на 15;
                         // Нумерация: по строкам — от 0 до 11, 
                         // по столбцам — от 0 до 14.

В некоторых языках программирования многомерные массивы создаются на основе одномерных, у которых элементы являются массивами.

Пример двумерного массива на JavaScript
    //Создание двумерного массива чисел: 
    var array = 
        11, 12, 13, 14, 15, 16], // Первая строка-массив
        21, 22, 23, 24, 25, 26], // Вторая
        31, 32, 33, 34, 35, 36  // Третья
    ];
    
    // Вывод массива на консоль:
    array.forEach((subArray) => {   // Для каждого под-массива,
       subArray.forEach((item) => { // для каждого его элемента,
           console.log(item);       // — вывести этот элемент на консоль.
       });
    });

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

В языках программирования, допускающих объявления программистом собственных типов, как правило, существует возможность создания типа «массив». В определении такого типа задаются типы и/или диапазоны значений каждого из индексов и тип элементов массива. Объявленный тип в дальнейшем может использоваться для определения переменных, формальных параметров и возвращаемых значений функций. Некоторые языки поддерживают для переменных-массивов операции присваивания (когда одной операцией всем элементам массива присваиваются значения соответствующих элементов другого массива).

Объявление типа «массив» в языке Паскаль
  type
    TArrayType = array ..9 of Integer; 
    (* Массивы, имеющие заданные параметры:
        1. Размер — 10 ячеек; 
        2. Тип элементов, пригодных для хранения — 
                — целые числа диапазона ,
        — объявляются типом операндов, называющимся "TArrayType". *)

  var
    arr1, arr2, arr3 TArrayType; 
    (* Объявление трёх переменных-массивов одного типа 
        (вышеуказанного "TArrayType"). *)

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

Специфические типы массивов

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

Основная статья: Динамический массив

Динамическими называются массивы, размер которых может изменяться во время выполнения программы. Обычные (не динамические) массивы называют ещё фиксированными или статическими.

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

  1. Описание динамического массива. На уровне языка это может быть специальная синтаксическая конструкция, на уровне библиотеки — библиотечный тип данных, значение которого объявляется стандартным образом. Как правило, при описании (создании) динамического массива указывается его начальный размер, хотя это и не обязательно.
  2. Операция определения текущего размера динамического массива.
  3. Операция изменения размера динамического массива.

Ниже приведён пример конструкций для работы с динамическими массивами на Delphi.

var  // Описания динамических массивов
  byteArray   Array of Byte;           // Одномерный массив
  multiArray  Array of Array of string;  // Многомерный массив
...
  SetLength(byteArray, 1); // Установка размера массива в 1 элемент.
  byteArray := 16;       // Запись элемента.
  SetLength(byteArray, Length(byteArray)+1); // Увеличение размера массива на единицу
  byteArrayLength(byteArray) - 1 := 10;    // Запись значения в последний элемент.
  WriteLn(byteArrayLength(byteArray) - 1]); // Вывод последнего элемента массива. 
...
  SetLength(multiArray, 20, 30); // Установка размера двумерного массива
  multiArray10,15 := 12;
  SetLength(multiArray, 10, 15); // Уменьшение размера 
  WriteLn(Length(multiArray), '  ', Length(multiArray])

Гетерогенные массивы

Гетерогенным называется массив, в разные элементы которого могут быть непосредственно записаны значения, относящиеся к различным типам данных. Массив, хранящий указатели на значения различных типов, не является гетерогенным, так как собственно хранящиеся в массиве данные относятся к единственному типу — типу «указатель». Гетерогенные массивы удобны как универсальная структура для хранения наборов данных произвольных типов. Реализация гетерогенности требует усложнения механизма поддержки массивов в трансляторе языка.

Размер массива

Массив в си должен иметь константный размер. Это значит, что невозможно, например, запросить у пользователя размер, а потом задать этот размер массиву.

printf("Enter length of array ");
scanf("%d", &length);

{
	float x;
}

Создание динамических массивов будет рассмотрено дальше, при работе с указателями и памятью

В некоторых случаях можно узнать размер массива с помощью функции sizeof.

#include <conio.h>
#include <stdio.h>

void main() {
	int A;
	//sizeof возвращает размер всего массива в байтах
	//Для определения количества элементов необходимо
	//разделить размер массива на размер его элемента
	int size = sizeof(A) / sizeof(int);

	printf("Size of array equals to %d", size);
	getch();
}

Но это вряд ли будет полезным. При передаче массива в качестве аргумента функции будет передаваться указатель, поэтому размер массива будет невозможно узнать.

Статические массивы удобны, когда заранее известно число элементов. Они предоставляют быстрый, но небезопасный доступ до элементов.

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

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

 
тип имя…;

int a;

Общее количество элементов в приведенном двумерном массиве определится как

КоличествоСтрок * КоличествоСтолбцов = 2 * 3 = 6.

Количество байт памяти, требуемых для размещения массива, определится как

КоличествоЭлементов * РазмерЭлемента = 6 * 4 = 24 байта.

Массивы

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

Массив в си определяется следующим образом
Например,
Мы получим массив с именем a, который содержит сто элементов типа int. Как и в случае с переменными, массив содержит мусор.

Для получения доступа до первого элемента, в квадратных скобках пишем его номер (индекс). Например

#include <conio.h>
#include <stdio.h>

void main() {

	int a;

	a = 10;
	a = 333;
	a = 234;
	printf("%d %d %d", a, a, a);

	getch();

}

Первый элемент имеет порядковый номер 0

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

Имя массива — это указатель на адрес памяти, где располагаются элементы массива.

Pascal: занятие № 5. одномерные массивы в паскалеРис. 1 Массив хранит адрес первого элемента. Индекс i элемента — это сдвиг на i*sizeof(тип) байт от начала

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

В си массив не хранит своего размера и не проверяет индекс массива на корректность. Это значит, что можно выйти за пределы массива и обратиться к памяти,
находящейся дальше последнего элемента массива (или ближе).

Специфические типы массивов

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

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

Пример динамического массива на Delphi

Пример динамического массива на Си

Пример динамического массива на С++

Гетерогенные массивы

Гетерогенным называется массив, в разные элементы которого могут быть непосредственно записаны значения, относящиеся к различным типам данных. Массив, хранящий указатели на значения различных типов, не является гетерогенным, так как собственно хранящиеся в массиве данные относятся к единственному типу — типу «указатель». Гетерогенные массивы удобны как универсальная структура для хранения наборов данных произвольных типов. Отсутствие их поддержки в языке программирования приводит к необходимости реализации более сложных схем хранения данных. С другой стороны, реализация гетерогенности требует усложнения механизма поддержки массивов в трансляторе языка. Гетерогенный массив как встроенный тип данных присутствует в языке PHP.

Массивы массивов

Многомерные массивы, как правило, реализованные как одномерные массивы, каждый элемент которых является ссылкой на другой одномерный массив.