Массивы в программировании: что это, виды и работа с данными

Массивы в программировании: что это, виды и работа с данными Полезное

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

Что такое массив и зачем он нужен

Элементы массива лежат в памяти подряд, одинакового размера. Зная адрес начала и номер элемента, программа вычисляет адрес ячейки простой арифметикой, поэтому чтение и запись по индексу идут за постоянное время O(1) — ради этого массив и используют. У классического (статического) массива при этом два ограничения: фиксированный размер и один тип элементов. Именно они отличают его от более гибких списков Python и массивов JavaScript.

Виды массивов

Массивы различают по трем признакам.

По числу измерений (размерности):

  • Одномерные — один индекс, элементы идут цепочкой. Такой массив по смыслу близок к вектору.
  • Двумерные — два индекса (строка и столбец), по форме это матрица (таблица).
  • Многомерные — три и более индексов. На практике выше трех измерений встречаются редко.

По изменяемости размера:

  • Статические (фиксированные) — размер задан при создании и не меняется. Так устроены массивы в C, C++, обычные массивы в Java.
  • Динамические — размер меняется во время работы программы: списки Python, массивы JavaScript, std::vector в C++, ArrayList в Java.

По типу элементов:

  • Однородные — все элементы одного типа (числовой массив, массив строк). Классический вариант для статических массивов.
  • Гетерогенные — элементы разного типа. Так работают список Python и массив JavaScript: в одной структуре можно держать число, строку и объект.

Отдельно стоят структуры, которые называют массивами по аналогии: ассоциативные массивы (доступ по ключу, а не по числовому индексу — словарь dict в Python, объект или Map в JavaScript) и разреженные массивы (хранят только непустые элементы). Строго говоря, это не массивы с непрерывной индексацией, а отдельные структуры данных.

Доступ по индексу

В большинстве языков индексация начинается с нуля: первый элемент имеет индекс 0, последний — на единицу меньше длины. Так устроены C, C++, Java, Python, JavaScript. Но есть исключения: в Pascal нижнюю границу можно задать произвольно, в Lua отсчет идет с единицы. Так что «индексация с нуля» — частое, но не универсальное правило.

Пример на Python (список — это динамический массив):

a = [10, 20, 30, 40, 50]
print(a[0])    # 10  (первый элемент)
print(a[2])    # 30  (третий по счету)
print(a[-1])   # 50  (последний, отрицательный индекс - от конца)
print(len(a))  # 5   (длина)

Двумерный массив в Python обычно представляют как список списков:

matrix = [
    [1, 2, 3],
    [4, 5, 6],
]
print(matrix[1][2])  # 6  (вторая строка, третий столбец)

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

Неверно:

a = [10, 20, 30]
print(a[3])   # обращаемся к четвертому элементу, а их три

Результат:

IndexError: list index out of range

Исправление — проверять границу или пользоваться длиной:

a = [10, 20, 30]
i = 3
if 0 <= i < len(a):
    print(a[i])
else:
    print("нет такого индекса")   # нет такого индекса

Важное отличие: Python, Java и JavaScript проверяют границы и сообщают об ошибке, а C и C++ границы не проверяют — выход за пределы массива ведет к неопределенному поведению (можно случайно прочитать или испортить чужую память). Это частая причина уязвимостей в C-коде.

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

Термины «массив» и «список» в разных языках означают разное, и это главный источник путаницы.

  • В C и C++ массив — статический блок памяти фиксированного размера, а динамический аналог — отдельный контейнер (std::vector).
  • В Python встроенного «массива» в узком смысле нет: тип list — это динамический массив, который растет и хранит разнотипные элементы. Компактный типизированный массив дают модуль array и библиотека NumPy (ndarray).
  • В JavaScript тип Array — тоже динамический и гетерогенный, ближе к списку, чем к классическому массиву.

Динамический массив внутри — обычный массив с запасом емкости: когда место кончается, он выделяет блок побольше и копирует данные. Поэтому добавление в конец в среднем стоит O(1) (амортизированно), а отдельная операция расширения — O(n).

Статический массив в C для контраста

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

#include <stdio.h>

int main(void) {
    int a[5] = {10, 20, 30, 40, 50};  // размер 5 фиксирован
    printf("%d\n", a[0]);   // 10
    printf("%d\n", a[2]);   // 30
    return 0;
}

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

Заполнение массива данными

Способ заполнения зависит от задачи. Основные варианты на Python.

Заполнение одинаковым значением и по формуле:

zeros = [0] * 5
print(zeros)     # [0, 0, 0, 0, 0]

squares = [i * i for i in range(1, 6)]   # квадраты чисел 1..5
print(squares)   # [1, 4, 9, 16, 25]

Заполнение случайными числами:

import random

nums = [random.randint(0, 10) for _ in range(5)]
print(nums)   # напр. [3, 9, 0, 7, 2], меняется от запуска

Заполнение вводом с клавиатуры (n чисел в одной строке через пробел) и добавление в конец по одному:

data = [int(x) for x in input().split()]   # ввод: 4 8 15 16 23
print(data)     # [4, 8, 15, 16, 23]

result = []
for i in range(1, 4):
    result.append(i * 10)   # добавляем в конец
print(result)   # [10, 20, 30]

В JavaScript массив наполняют методами push (в конец), unshift (в начало) и splice (вставка с нужной позиции):

const arr = [2, 3];
arr.push(4);      // [2, 3, 4]
arr.unshift(1);   // [1, 2, 3, 4]
console.log(arr); // [1, 2, 3, 4]

Сложность операций

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

Операция Массив (по индексу) Комментарий
Доступ по индексу O(1) адрес вычисляется напрямую
Изменение элемента по индексу O(1) перезапись ячейки
Поиск по значению O(n) перебор в худшем случае
Добавление в конец (динамический) O(1) амортизированно иногда расширение за O(n)
Вставка/удаление в начало или середину O(n) нужен сдвиг остальных элементов
Обход всех элементов O(n) один проход по массиву

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

Выводы

  • Массив — это набор элементов подряд в памяти с доступом по целочисленному индексу; чтение и запись по индексу идут за постоянное время O(1).
  • Виды массивов различают по размерности (одномерные, двумерные, многомерные), по изменяемости размера (статические и динамические) и по типу элементов (однородные и гетерогенные).
  • Индексация чаще всего начинается с нуля, но это не универсальное правило: в Pascal, Lua и ряде языков нижняя граница другая.
  • «Массив» и «список» означают разное: в Python list — это динамический массив, типизированный дают array и NumPy, а в C массив статический и фиксированного размера.
  • Массив выгоден для доступа по индексу и добавления в конец, но проигрывает на вставках и удалениях в начале или середине — там уместнее другие структуры данных.

Где применяется / связь с практикой

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

Разобраться на практике удобнее всего на Python: списки, срезы, включения (comprehensions) и модуль array дают все виды массивов, а порог входа низкий. Системно пройти основы — от переменных и циклов до структур данных — можно на курсе Python для начинающих. Если хочется сначала посмотреть формат и уровень без оплаты, начните с бесплатных вебинаров — там разбирают реальные задачи и отвечают на вопросы.

Смежные темы: Все о векторах от А до Я, Программирование на C: описание языка, перспективы, основы разработки, Дерево в IT: описание и особенности.

FAQ

Чем массив отличается от списка в Python?
Тип list — это и есть динамический массив общего назначения: хранит разнотипные элементы и меняет размер. Отдельный «массив» дают модуль array и библиотека NumPy — они типизированные и компактнее, быстрее на однотипных числовых данных.

Почему индекс во многих языках начинается с нуля?
Индекс исторически означает смещение от начала массива: у первого элемента оно равно нулю. Так удобнее адресной арифметике в C, и традиция перешла в большинство современных языков. В Pascal или Lua отсчет может начинаться иначе.

Можно ли хранить в одном массиве значения разных типов?
Зависит от языка. В статических массивах C, C++ и Java — нет, тип фиксирован при объявлении. В списке Python и массиве JavaScript — да, они гетерогенные, но без необходимости типы лучше не смешивать: однородные данные обрабатывать проще и быстрее.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)