Счёт в программировании начинается с нуля, потому что индекс — это не порядковый номер элемента, а его смещение от начала массива в памяти. Адрес элемента считается по формуле адрес = база + индекс × размер. У самого первого элемента смещение равно нулю: он лежит ровно там, где начинается массив. Поэтому его индекс — 0, а не 1.

Коротко, что стоит за этим правилом:

  • Адресная арифметика. Индекс 0 у первого элемента убирает лишнее вычитание при доступе к памяти.
  • Полуоткрытые интервалы. Диапазон 0 ≤ i < N задаёт массив из N элементов без «+1» и «−1».
  • Длина = последний индекс + 1. В массиве из 10 элементов последний индекс — 9.
  • Срезы и циклы. Половинки диапазона стыкуются встык, длина среза = верхняя граница минус нижняя.
  • Не универсальный закон. Fortran, MATLAB, Lua, R и Julia считают с единицы — и работают.

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

Как индекс превращается в адрес: адрес = база + индекс × размер элемента
Как индекс превращается в адрес: адрес = база + индекс × размер элемента

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

Если индексация начинается с нуля, формула адреса максимально простая:

адрес(i) = база + i × размер_элемента // первый элемент: база + 0 × размер = база // второй элемент: база + 1 × размер // третий элемент: база + 2 × размер

Индекс здесь буквально отвечает на вопрос «сколько элементов лежит перед этим». Перед первым элементом не лежит ничего — значит, его индекс 0. Перед вторым лежит один элемент — индекс 1. Это не соглашение ради красоты, а прямое отражение того, как данные размещены в памяти.

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

адрес(i) = база + (i − 1) × размер_элемента

Это лишнее вычитание на каждый доступ к элементу. В языке C, где массивы напрямую связаны с указателями, нулевая индексация даёт более простую реализацию: запись arr[i] — это ровно *(arr + i), то есть «взять то, что лежит по адресу база плюс i». Индекс здесь — смещение от стартовой позиции массива, поэтому у первого элемента смещение равно нулю.

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

ОБЗОРНЫЙ ПРАКТИКУМ ПО НАШУМЕВШИМ НЕЙРОСЕТЯМ
Нейросети DEEPSEEK И QWEN За 2 часа сделаем полный обзор новых мощных ИИ-моделей, которые бросают вызов нейросети ChatGPT
ТОП-подарки всем участникам лекции:
  • Возможность получить Доступ в Нейроклуб на целый месяц
  • Как ИИ ускоряет работу и приносит деньги
  • За 2 часа вы получите четкий план, как начать работать с ИИ прямо сейчас!

Заметка Дейкстры: почему это не только про железо

В 1982 году Эдсгер Дейкстра написал короткую заметку EWD831 «Why numbering should start at zero». Он показал, что нулевая индексация выигрывает не только из-за адресной арифметики, но и математически — из-за того, как удобнее записывать диапазоны.

Диапазон целых чисел можно ограничить четырьмя способами, комбинируя и < на нижней и верхней границе. Дейкстра разбирает все четыре и выбирает полуоткрытый интервал вида a ≤ i < b — включаем нижнюю границу, исключаем верхнюю. Его аргументы:

  • Длина считается вычитанием. Число элементов в a ≤ i < b равно ровно b − a. Никаких «+1».
  • Пустой диапазон не ломается. Пустая последовательность записывается как a ≤ i < a. Если бы верхняя граница включалась, при сжатии до пустоты её пришлось бы делать «неестественной» — уходить ниже нижней.
  • Диапазоны стыкуются встык. Два соседних куска примыкают друг к другу, когда верхняя граница одного равна нижней границе второго. Ни зазора, ни нахлёста.

А дальше — ключевой шаг. Если зафиксировать полуоткрытый интервал a ≤ i < b и спросить, с чего начинать нумерацию массива из N элементов, то старт с единицы даёт диапазон 1 ≤ i < N+1, а старт с нуля — более чистый 0 ≤ i < N. Формулировка Дейкстры: порядковый номер элемента равен числу элементов, стоящих перед ним. Именно это и означает индекс 0 у первого.

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

Интуиция: индекс — это этаж, а не порядковый номер

Есть бытовая аналогия, которая снимает первичное непонимание. В британской системе первый этаж дома — это ground floor, «нулевой»: вы стоите на земле, поднялись на 0 пролётов. Следующий этаж — «первый», один пролёт вверх. Число этажа здесь означает не «какой по счёту», а «на сколько выше начала».

Индекс массива устроен так же. arr[0] — это «нулевое смещение от начала», вы никуда не сдвинулись. arr[3] — «сдвинуться на три элемента вправо». Как только перестаёшь читать индекс как «номер по порядку» и начинаешь читать как «на сколько шагов от старта», нулевая нумерация становится единственно логичной.

Вывод. Индекс отвечает на «на сколько дальше от начала», а порядковый номер («первый», «второй») — это уже человеческий перевод, который в C-подобных языках получается прибавлением единицы к индексу.

Что даёт нулевая нумерация на практике

Три вещи, ради которых это соглашение живёт десятилетиями.

Длина и последний индекс связаны без поправок

В массиве из N элементов индексы идут от 0 до N−1, а длина равна N. Последний индекс — это длина − 1. Проверка «вышли ли за границу» — это просто i < length, без сравнения с length − 1.

Модульная арифметика ложится ровно

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

# Python: следующий индекс по кругу next_i = (i + 1) % len(arr) # при i = len-1 получаем 0 — вернулись в начало # при единичной индексации пришлось бы писать # ((i - 1 + 1) % len) + 1 — лишние сдвиги туда-обратно

Хеш-таблицы, кольцевые буферы, разбивка на N корзин — везде значение % N даёт готовый индекс от 0 до N−1.

Двумерные структуры адресуются проще

Матрицу из R строк по C столбцов в памяти обычно раскладывают в один линейный массив (построчно). Адрес элемента [row][col] считается так:

смещение = row × C + col // row=0, col=0 → 0 (самое начало) // row=1, col=0 → C (начало второй строки) // row=2, col=3 → 2×C + 3

С нулевой индексацией формула чистая и без поправок. При единичной пришлось бы писать (row − 1) × C + (col − 1) с двумя вычитаниями. Чем больше измерений, тем заметнее выигрыш.

Циклы пишутся одинаково

Стандартный цикл по массиву в C-подобных языках выглядит так:

for (int i = 0; i < n; i++) { // arr[i] — от первого до последнего }

Верхняя граница i < n — это тот самый полуоткрытый интервал Дейкстры. Число итераций равно n, читается с одного взгляда.

Вывод. Нулевая индексация убирает поправки «плюс-минус один» из формул длины, из модульной арифметики и из условий циклов.

Языки, которые считают с единицы

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

Язык Первый индекс Почему так
C, C++, Java, Python, JavaScript, C#, Go, Rust 0 Индекс = смещение от начала, близость к указателям
Fortran 1 (по умолчанию) Язык под математику; нижнюю границу можно задать вручную
COBOL 1 Соответствие обычным порядковым номерам
MATLAB 1 Матрицы и векторы как в линейной алгебре
R 1 Статистика, работа с таблицами данных
Lua 1 (идиоматично) Таблицы принято нумеровать с 1
Julia 1 Ориентация на научные вычисления
Pascal, Ada Любой Нижнюю границу диапазона задаёт программист

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

Off-by-one: главная ошибка на нулевой индексации

Ошибка «на единицу» (off-by-one) — самая частая цена нулевой нумерации. Возникает там, где путают «длину» и «последний индекс».

# Ошибка: выход за границу массива arr = [10, 20, 30, 40, 50] # длина 5, индексы 0..4 for i in range(len(arr) + 1): # 0,1,2,3,4,5 — лишняя 5 print(arr[i]) # IndexError на i = 5

Последний валидный индекс — len(arr) − 1, то есть 4. Обращение к arr[5] вылетает за пределы. Обратная версия ошибки — начать цикл с 1 и потерять первый элемент.

Практические ориентиры, которые снимают большинство таких промахов:

  • Последний индекс — всегда длина − 1, а не длина.
  • В условии цикла держите i < длина, а не i <= длина.
  • Число элементов в a ≤ i < b — это b − a. Если считаете длину и получаете «на единицу больше», проверьте границу.

Вывод. Off-by-one лечится не осторожностью, а привычкой держать в голове полуоткрытый интервал: нижняя граница включена, верхняя — нет.

Связь со срезами

Срезы (slices) — прямое продолжение той же логики. В Python arr[a:b] берёт элементы с индекса a включительно до b не включая — тот самый полуоткрытый интервал.

arr = [10, 20, 30, 40, 50] arr[0:2] # [10, 20] — длина 2 − 0 = 2 arr[2:5] # [30, 40, 50] — длина 5 − 2 = 3 arr[2:2] # [] — пустой срез, a = b

Из-за исключённой верхней границы срезы стыкуются без нахлёста: arr[0:2] и arr[2:5] вместе дают весь массив, элемент с индексом 2 не задваивается. Длина среза считается вычитанием границ, а пустой срез arr[2:2] не требует особой обработки. Ровно те три свойства, которые Дейкстра выписал для полуоткрытого интервала, — они и работают в срезах каждый день.

FAQ

С какого числа начинается счёт в программировании?

В большинстве распространённых языков (C, C++, Java, Python, JavaScript, C#, Go, Rust) индексация массивов начинается с нуля: первый элемент имеет индекс 0. Но это не всеобщее правило — Fortran, MATLAB, R, Lua и Julia считают с единицы.

Почему первый элемент массива имеет индекс 0?

Потому что индекс — это смещение от начала массива в памяти, а не порядковый номер. Первый элемент лежит в самом начале, его смещение равно нулю. Формула адреса база + индекс × размер для него даёт просто база.

Что такое ошибка off-by-one?

Это промах на единицу при работе с границами: обращение к arr[длина] вместо arr[длина − 1], лишняя или недостающая итерация цикла. Частая причина — путаница между длиной массива и индексом последнего элемента.

Почему массив из N элементов имеет индексы от 0 до N−1?

Потому что диапазон индексов — полуоткрытый: 0 ≤ i < N. Всего значений ровно N, они идут от 0 до N−1. Длина массива при этом равна N, а последний индекс — N−1.

Все ли языки считают с нуля?

Нет. Нулевая индексация доминирует в C-подобных и веб-языках, но Fortran, COBOL, MATLAB, R, Lua и Julia используют единичную. Pascal и Ada позволяют задать нижнюю границу диапазона вручную. Это инженерное соглашение, а не закон.

Обновлено в 2026 году. Первоисточник аргументации о полуоткрытых интервалах — заметка Дейкстры EWD831.

РОССИЙСКИЕ НЕЙРОСЕТИ ДЛЯ ЖИЗНИ И КАРЬЕРЫ В 2025
Присоединяйся к онлайн-вебинару.
В прямом эфире разберем и потестируем лучшие на сегодняшний день отечественные ИИ!
Вы узнаете о том:
  • Выполним базовые задачи на российских нейросетях и посмотрим на результаты!
  • Файл-инструкцию «Как сделать нейро-фотосессию из своего фото бесплатно, без иностранных карт и прочих сложностей»
  • Покажем 10+ способов улучшить свою жизнь с ИИ каждому — от ребенка и пенсионера до управленца и предпринимателя
Участвовать бесплатно
ОБЗОРНЫЙ ПРАКТИКУМ ПО НАШУМЕВШИМ НЕЙРОСЕТЯМ
Нейросети DEEPSEEK И QWEN
За 2 часа сделаем полный обзор новых мощных ИИ-моделей, которые бросают вызов нейросети ChatGPT
Вы узнаете:
  • Возможность получить Доступ в Нейроклуб на целый месяц
  • Как ИИ ускоряет работу и приносит деньги
  • За 2 часа вы получите четкий план, как начать работать с ИИ прямо сейчас!
Участвовать бесплатно