АШШумилкин А. О.ОАиП · ПиТПМ
ОАиП · Лекция 05

Лекция №5. Организация данных в виде массивов.

2 335 слов9 разделов2 иллюстраций
↓ Скачать Markdown

§5.1 Определение массива

Ранее мы объявляли переменные для хранения одиночного значения типа int, char или string, даже если использовалось несколько их экземпляров. Однако можно объявить коллекцию объектов, например, 20 целых чисел или стаю котов.

Определение слова массив (array) в словаре довольно близко к тому, что мы хотим понять. Согласно словарю Вебстера, массив — это «группа элементов, формирующих полный набор, например, массив солнечных панелей».

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

Массив

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

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

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

text

// Выделяем 30 целочисленных переменных
int testResultStudent1;
int testResultStudent2;
int testResultStudent3;
// ...
int testResultStudent30;

С использованием массива всё гораздо проще. Следующая строка эквивалентна коду, приведенному выше:

text

int testResult[30];

В объявлении переменной массива мы используем квадратные скобки [], чтобы сообщить компилятору, что это переменная массива (а не обычная переменная), а в скобках — количество выделяемых элементов (это называется длиной или размером массива).

В примере, приведенном выше, мы объявили фиксированный массив с именем testResult и длиной 30. Фиксированный массив (или «_массив фиксированной длин_ы») представляет собой массив, размер которого известен во время компиляции. При создании testResult, компилятор выделит 30 целочисленных переменных.

§5.2 Объявление и инициализация статических массивов

Объявление статических массивов в C++ и C# отличается.

1. Объявление статических массивов в С++

Для объявления массива на языке программирования С++ используется следующий синтаксис:

text

тип_элемента имя_массива [количество_элементов] = {необязательные исходные значения};

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

cpp

int MyNumbers [5] = {34, 56, -21, 5002, 365};

Все элементы массива можно также инициализировать одним значением:

cpp

int MyNumbers [5] = {100};

Здесь все элементы массива MyNumbers инициализируются значением 100. Вы можете также инициализировать только часть элементов массива:

cpp

int MyNumbers [5] = {100, 200};

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

Если исходное количество элементов в массиве неизвестно, можно не указывать его:

cpp

int MyNumbers [] = {100, 200, 300};

Приведенный выше код создает массив длиной равной трем.

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

2. Объявление статических массивов в С#

Объявление массива в С# похоже на объявление переменной за тем исключением, что после указания типа ставятся квадратные скобки:

text

тип_элемента[] имя_массива;

Например, определим массив целых чисел:

csharp

int[] numbers;

После определения переменной массива мы можем присвоить ей определенное значение:

csharp

int[] numbers = new int[5];

Здесь вначале мы объявили массив nums, который будет хранить данные типа int. Далее используя операцию new, мы выделили память для пяти элементов массива: new int[5]. Число 5 – здесь является длиной массива. При таком определении все элементы получают значение по умолчанию, которое предусмотрено для их типа. Для типа int значение по умолчанию – 0.

Также мы сразу можем указать значения для этих элементов:

csharp

int[] nums2 = new int[4] { 1, 2, 3, 5 };
int[] nums3 = new int[] { 1, 2, 3, 5 };
int[] nums4 = new[] { 1, 2, 3, 5 };
int[] nums5 = { 1, 2, 3, 5 };

Все перечисленные выше способы будут равноценны.

§5.3 Доступ к данным, хранящимся в массиве

Чтобы использовать данные в массиве, необходимо обратиться к конкретному элементу. Это делается с помощью индекса — номера позиции элемента в массиве.

Доступ к элементам массива

— одна из основных операций при работе с массивами. Она позволяет читать значения, изменять их и обрабатывать по одному.

Каждый элемент массива имеет уникальный номер позиции, называемый индексом. Важно понимать, что индекс массива начинается с 0, то есть первый элемент — под индексом 0, второй — 1, ..., последний — n-1, где n — длина массива.

wireframe

┌──────────────────────────────────────────────────┐

│                      МАССИВ                      │

├───────────────────┬───────┬───────┬──────────────┤

│ Элементы (Values) │  10   │  20   │  30   │  40  │

├───────────────────┼───────┼───────┼───────┼──────┤

│ Индексы (Indexes) │   0   │   1   │   2   │   3  │

└───────────────────┴───────┴───────┴───────┴──────┘

Для доступа к элементу используется квадратная скобка [] после имени массива:

text

имя_массива[индекс]

Это выражение можно использовать:

  • Справа — чтобы прочитать значение.
  • Слева — чтобы записать (изменить) значение.

Чтобы получить значение элемента, используйте индекс в скобках:

int[] numbers = {10, 20, 30};
int x = numbers[1];  // x = 20
Console.WriteLine(numbers[0]); // вывод: 10

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

Пример:

text

numbers[1] = 99; // теперь массив: {10, 99, 30}

Массивы изменяемы

— их элементы можно менять после создания, но у статчиеских массивов нельзя менять размер!

Выражение массив[индекс] ведёт себя точно так же, как обычная переменная. То есть, его можно использовать в:

  • В выражениях:

text

int sum = numbers[0] + numbers[1];
  • В условиях:

text

if (numbers[2] > 0) 
{
	// Какое-то необходимое действие 
} 
  • В вызовах функций:

text

PrintValue(numbers[1]); 
  • В циклах:

csharp

for (int i = 0; i < 5; i++)
{
	Console.WriteLine(numbers[i]);
}

Информация

Очень важно! Индекс должен быть в допустимом диапазоне от 0 до «длина – 1». В C#, в случае, выхода за границы, Вы получите исключение «IndexOutOfRangeException». В С++ же нет проверки. Обращение к arr[100] при массиве из 5 элементов — ошибка времени выполнения , может повредить память, вызвать сбой.

Помимо того, что индекс может быть константой, как говорилось выше, индекс так же может быть переменной: arr[i] и выражением: arr[2 * j - 1].

Пример:

int i = 1;
int j = i + 1;
Console.WriteLine(arr[j]); // arr[2]

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

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

Но что, если нам теперь нужно использовать массив для моделирования солнечных панелей?

Рисунок 1 – Моделирование солнечных панелей

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

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

§5.4.1 Двумерные массивы

Двумерный массив

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

Общая структура двумерных статических и динамических массивов выглядит следующим образом:

wireframe

┌──────────────────────────────────────────────────────────────────┐

│                    ИНДЕКСАЦИЯ МАТРИЦЫ [i, j]                     │

├───────────────┬───────────┬───────────┬───────────┬──────────────┤

│               │    [0]    │    [1]    │    [2]    │    [3]       │

├───────────────┼───────────┼───────────┼───────────┼──────────────┤

│      [0]      │    10     │    20     │    30     │    40        │

│      [1]      │    50     │    60     │    70     │    80        │

│      [2]      │    90     │   100     │   110     │   120        │

└───────────────┴───────────┴───────────┴───────────┴──────────────┘

Обращение к элементу:

  • matrix[0][1] → 20 (первая строка, второй столбец)
  • matrix[2][3] → 120 (третья строка, четвёртый столбец)

В C# двумерные массивы объявляются с использованием запятой в скобках: тип[,] имя:

text

int[,] matrix; // объявление

А для того, чтобы создать двумерный массив, необходимо инициализировать его размерность:

csharp

matrix = new int[3, 4]; 
// 3 строки, 4 столбца, все элементы = 0

Так же его сразу же можно проинициализировать:

csharp

int[,] matrix = {
    {10, 20, 30, 40},
    {50, 60, 70, 80},
    {90, 100, 110, 120}
};

В отличие от C#, где двумерный массив — это единый объект-таблица, в C++ нет встроенного понятия «матрица».

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

cpp

int matrix[3][4] = {
    {10, 20, 30, 40},
    {50, 60, 70, 80},
    {90, 100, 110, 120}
};

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

Объявление без инициализации происходит аналогично языку С#: «int matrix[3][4];».

Так же, что в С++, что в C#, элементы массива можно заполнять поэтапно:

matrix[0, 0] = 10;
matrix[0, 1] = 20;
// и т.д.

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

массив[строка, столбец]

Предположим, мы хотим вывести элемент с индексом «1, 2», на двух языках:

Console.WriteLine(matrix[1, 2]); 

Как и у одномерных массивов, у двумерных массивов есть размер, только в отличии от одномерного, размерностью счиатется «количество строк х количество столбцов». Для того, чтобы в C# получить размер массива, используются два метода:

csharp

int rows = matrix.GetLength(0); // строки
int cols = matrix.GetLength(1); // столбцы

В С++ встроенных функций для получения размера массивов нет. В этом случае, размеры необходимо хранить отдельно или вычислить с помощью sizeof:

cpp

// Первый способ
int rows = 3;
int cols = 4;

// Второй способ
int rows = sizeof(matrix) / sizeof(matrix[0]);// строки
int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);// столбцы
// Только стоит учитывать, что sizeof
// только для статических массивов, объявленных в той же области

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

Массивы могут иметь и большее количество измерений. Объявление трехмерного массива могло бы выглядеть так:

text

int[,,] nums3 = new int[2, 3, 4];

Или на примере языка С++ объявим четырехмерный массив:

cpp

int arr[2][3][4][5];// четырёхмерный массив: 2 × 3 × 4 × 5

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

Трёхмерные, четырёхмерные и вообще N-мерные массивы в C++ и C# (а также в других языках программирования) применяются в различных областях, где данные естественным образом представляются в виде многомерных структур, например, при разработке игр, где требуется трехмерная графика, и так далее.

Например, трехмерный массив при его выводе будет в следующем формате:

wireframe

┌──────────────────────────────────────────────────────────────────┐

│                      ТРЁХМЕРНЫЙ МАССИВ [2, 3, 4]                 │

├──────────────────────────────────────────────────────────────────┤

│                        СЛОЙ 0 (matrix[0, i, j])                  │

├──────────────────────────────────────────────────────────────────┤                                                               

│               │ Столбец 0 │ Столбец 1 │ Столбец 2 │ Столбец 3    │

├───────────────┼───────────┼───────────┼───────────┼──────────────┤

│ Строка 0      │     3     │    54     │     8     │              │

│ Строка 1      │     5     │    88     │     9     │              │

│ Строка 2      │     2     │    31     │     4     │              │

├──────────────────────────────────────────────────────────────────┤

│                        СЛОЙ 1 (matrix[1, i, j])                  │

├──────────────────────────────────────────────────────────────────┤ 

│               │ Столбец 0 │ Столбец 1 │ Столбец 2 │ Столбец 3    │

├───────────────┼───────────┼───────────┼───────────┼──────────────┤

│ Строка 0      │     2     │    25     │     7     │              │

│ Строка 1      │     5     │    29     │     9     │              │

│ Строка 2      │     9     │    41     │     5     │              │

└──────────────────────────────────────────────────────────────────┘

§5.4.3 «Зубчатые массивы»

От многомерных массивов надо отличать массив массивов или так называемый «зубчатый массив»:

csharp

int[][] nums = new int[3][];
nums[0] = new int[2] { 1, 2 };
// выделяем память для первого подмассива
nums[1] = new int[3] { 1, 2, 3 };
// выделяем память для второго подмассива
nums[2] = new int[5] { 1, 2, 3, 4, 5 };
// выделяем память для третьего подмассива

Важное уточнение

С++ зубчатые массивы обозначаются с помощью указателей на указатель о чем будет рассмотрено во 2-м семестре курса «Основы Алгоритмизации и Программирования».

Здесь две группы квадратных скобок указывают, что это массив массивов, то есть такой массив, который в свою очередь содержит в себе другие массивы. Причем длина массива указывается только в первых квадратных скобках, все последующие квадратные скобки должны быть пусты: new int[3][]. В данном случае у нас массив nums содержит три массива. Причем размерность каждого из этих массивов может не совпадать.

wireframe

┌──────────────────────────────────────────────────────────────────┐

│                   ЗУБЧАТЫЙ МАССИВ (C#)                           │

├───────────────┬──────────────────────────────────────────────────┤

│ Индекс (nums) │ Ссылка на подмассив (значения)                   │

├───────────────┼──────────────────────────────────────────────────┤

│    nums[0]    │ ┌───┬───┐                                        │

│               │ │ 1 │ 2 │ (размер: 2)                            │

│               │ └───┴───┘                                        │

│    nums[1]    │ ┌───┬───┬───┐                                    │

│               │ │ 1 │ 2 │ 3 │ (размер: 3)                        │

│               │ └───┴───┴───┘                                    │

│    nums[2]    │ ┌───┬───┬───┬───┬───┐                            │

│               │ │ 1 │ 2 │ 3 │ 4 │ 5 │ (размер: 5)                │

│               │ └───┴───┴───┴───┴───┘                            │

└───────────────┴──────────────────────────────────────────────────┘

Примеры всех массивов можно рассмотреть ниже:

Рисунок 1 – Виды массивов в языках программирования

Причем можно использовать в качестве массивов и многомерные:

csharp

int[][,] nums = new int[3][,]
{
    new int[,] { {1,2}, {3,4} },
    new int[,] { {1,2}, {3,6} },
    new int[,] { {1,2}, {3,5}, {8, 13} }
}; 

§5.5. Массивы в параметрах методов (C#)

Массивы часто используются как входные данные для методов. В C# передача массива в метод — простая и удобная операция, поскольку массивы являются ссылочными типами.

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

Чтобы принять массив в качестве параметра, укажите тип массива в списке параметров:

csharp

void PrintArray(int[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        Console.Write(arr[i] + " ");
    }

    Console.WriteLine();
}

Для того, чтобы вызвать метод с массивом необходимо просто создать массив и передать в аргумент:

csharp

int[] numbers = {1, 2, 3, 4, 5};
PrintArray(numbers); // вывод: 1 2 3 4 5

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

csharp

void DoubleValues(int[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i] *= 2;
    }
}

int[] data = {1, 2, 3};
DoubleValues(data);
Console.WriteLine(data[0]); // 2 — исходный массив изменён!

§5.6 Массивы и циклы

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

Например:

csharp

for (int i = 0; i < arr.Length; i++)
{
    Console.WriteLine(arr[i]);
}

Цикл for идеально подходит для работы с массивами, потому что:

  • Позволяет управлять индексом (i).
  • Условие i < arr.Length гарантирует безопасный доступ.
  • Подходит для любого типа массива.

Вывод элементов массива на языке С# и C++ работает аналогично:

int[] numbers = {10, 20, 30, 40, 50};
for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine($"Элемент [{i}] = {numbers[i]}");
}

В C# и в С++ есть специальный цикл foreach, предназначенный для перебора всех элементов коллекции, включая массивы.

Он не работает с индексами, но защищает от выхода за границы.

csharp

int[] numbers = {1, 2, 3, 4, 5};
foreach (int value in numbers)
{
    Console.Write(value + " ");
}

В лекции №3 мы узнали, что в С++ есть аналог цикла foreach из С#, его сигнатура выглядит следующим образом:

text

for(type variable_name : array)

Для примера перепишем код выше на С++ с использованием аналога цикла foreach:

cpp

int numbers[] = {1, 2, 3, 4, 5};
for (int i : numbers)
{
    std::cout << "Элемент [" << i << "];
}

Для двумерных и более массивов используются вложенные циклы. Для начала напомним, что язык С# предоставляет встроенные методы для вычисления размера, например, метод – это GetLenght(dim), где dim принимает 3 значения, 0 – количество строк, 1 – количество столбцов, 2 – третье измерение (для трехмерных и более массивов).

В С++ нет встроенных методов, поэтому приходится выполнять небольшую логику:

cpp

int matrix[3][4];
int rows = sizeof(matrix) / sizeof(matrix[0]); // 3
int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);  // 4

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

int[,] matrix = { // С#
    {1, 2},
    {3, 4}
};

for (int i = 0; i < matrix.GetLength(0); i++)
{
    for (int j = 0; j < matrix.GetLength(1); j++)
    {
       Console.Write(matrix[i, j] + " ");
    }
        
    Console.WriteLine();
}