Почему в программировании счёт всегда начинается с нуля, а не с единицы

Почему в программировании счёт всегда начинается с нуля, а не с единицы

Потому что так удобно компьютеру

В проектах мы периодически говорим, что компьютер почти всё начинает считать с нуля, а не с единицы. Это значит, что первый элемент массива вызывается командой arr[0], второй — arr[1], а шестой — arr[5]. Объясняем, почему так.

Память, переменные и первый байт

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

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

Минимальная ячейка, в которой может лежать единица или ноль, называется бит. Но в бите можно закодировать только 1 или 0 или «да/нет». Чтобы кодировать что-то более сложное, нужно взять не один бит, а группу. В компьютере принято объединять биты в группы по 8, такая группа называется «байт». 

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

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

00000000 — ноль, минимальное значение байта

00000001 — один

00000010 — два

00000011 — три

00000100 — четыре

00000101 — пять

11111110 — двести пятьдесят четыре

11111111 — двести пятьдесят пять, максимальное значение байта

Обратите внимание, что минимальное значение, которое можно хранить в одном байте — не единица, а именно ноль.

Логика компьютера

Теперь представьте: компьютер исполняет программу, где ему нужно обойти какой-нибудь массив и что-то там подсчитать. Для обхода массива ему нужна область памяти для подсчёта. Что происходит дальше:

  1. Процессор находит свободное место в памяти и запоминает: «Вот тут у меня будет лежать счётчик для этого массива».
  2. Место в памяти обнуляется, чтобы там не было никакого мусора. Мало ли там какие данные лежали от прошлой программы? 
  3. Обнулённый счётчик — это 00000000, то есть ноль.
  4. Раз счётчик уже есть и у него есть валидное значение «ноль», то компьютер начинает считать именно с нуля.

Благодаря тому что компьютер начинает считать с нуля, в 8 бит он может поместить 256 значений: от 0 до 255. Если бы компьютер считал от 1 до 255, в 8 бит поместилось бы 255 значений — то есть на одно меньше. 

Можно ли всё-таки считать с 1?

Можно, но это необычно. Чтобы компьютер начал считать всё с единицы, нам нужно объяснить ему, как это делать. Например, подход может быть таким:

  1. Мы создаём массив, в котором элементов на один больше, чем нам нужно. Например, если мы собираемся там хранить 100 чисел, делаем массив на 101 элемент.
  2. Когда начинаем его заполнять, то первым элементом мы указываем единицу: arr[1] = 15, вторым — двойку и так до конца.
  3. Так мы заполним 100 элементов, которые можно начинать считать с единицы, а нулевой элемент останется незаполненным.

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

Как ошибаются из-за счётчиков с нуля

Самая частая ошибка при начале счёта с нуля — путать между собой длину массива и индекс последнего элемента. Следите за руками:

  • Мы сделали массив на 100 элементов.
  • Первый элемент массива — это arr[0], а последний — arr[99].
  • Когда мы запросим длину массива, то в ответ получим число 100.
  • А если мы обратимся к arr[100], то получим ошибку, потому что элемента с индексом 100 в массиве нет.

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

Как грамотно обойти массив с помощью счётчиков на разных языках (и не запутаться в единицах и нулях)

Представим, что у нас есть массив arr[], в котором хранится 100 чисел, и нам нужно вывести их на экран. 

Классический алгоритм такой:

  1. Заводим переменную для счётчика, обычно её обозначают буквой i.
  2. В i записывается ноль. Начинается цикл. 
  3. Внутри цикла берётся массив. Из него достаётся элемент под номером i, то есть соответствующий текущему номеру прохода цикла. Так как в начале алгоритма в счётчике ноль, то мы получим нулевой элемент цикла (то есть по-человечески — первый). 
  4. Когда шаг цикла выполнен, в переменную i добавляется единица. 
  5. Теперь цикл повторяется, но из массива достаётся не нулевое, а первое значение (то есть по-человечески — второе).
  6. Цикл повторяется до тех пор, пока i меньше, чем длина массива. 

Есть хитрость в выходе из цикла: значение счётчика i должно быть меньше, чем длина массива (а не «меньше или равно»).

Дело в том, что длина цикла измеряется по-человечески — 1, 2, 3 и далее. А счётчик работает по-компьютерному — 0, 1, 2… Это значит, что в массиве из 100 чисел длина массива будет 100, а максимальное значение счётчика — 99. 

Получается, что если повторять цикл вплоть до i < arr.length, то он завершится корректно при i == 99. Это стандартная практика. А если крутить цикл до i <= arr.length, то цикл исполнится 101 раз, и при i == 100 будет ошибка: этого элемента в массиве нет.

Пример такого кода в JavaScript:

for (var i = 0; i++, i<arr.length) { console.log(arr[key]) }

В Python:

for i in range(len(arr)):
    print(arr[i])

В C++:

for(int i = 0; i < length; i++)
{
   std::cout << arr[i];
}

Обход массивов вообще без счётчиков (и связанных с ними нулей)

В некоторых языках есть более компактная версия этой конструкции, которая буквально означает «Обойди этот объект». Вот примеры в JavaScript:

for (var i in arr) { console.log(arr[i]) }

В Python: 

for i in arr:
    print(i)

Здесь нужно смотреть на слово in, что буквально означает «по всем составляющим этого объекта». В разных языках у него разное значение

  • В JavaScript в переменную i положат номера элементов массива arr[], то есть эта переменная будет работать как счётчик. Фраза i in arr означает «для каждого номера элемента массива arr: положи этот номер в переменную i».
  • В Python фраза i in arr означает «возьми сами элементы массива arr и положи их по очереди в переменную i»

То есть JavaScript использует for…in для простого управления счётчиками, а Python вообще избавляется от понятия счётчика и сразу отдаёт в переменную то значение, которое он сейчас перебирает. 

Если совсем просто: 

  • Допустим, у нас есть массив arr[], который состоит из чисел 2, 12, 85, 6.
  • В конструкции на JavaScript в переменной i будут числа 0, 1, 2, 3.
  • А в конструкции на Python в переменной i будут 2, 12, 85, 6.

Но это если уже совсем угореть. Всё, хорош, и так понятно: компьютеры считают с нуля. 

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Виталий Вебер

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
Чем отличается авторизация от аутентификации
Чем отличается авторизация от аутентификации

Как усложнить жизнь злоумышленникам.

medium
Что такое XML
Что такое XML

Логическая разметка данных

easy
Что такое флаг в программировании
Что такое флаг в программировании

Веселье с флагами, только не веселье

easy
Дизайнер в ИТ: зачем он нужен и как им стать
Дизайнер в ИТ: зачем он нужен и как им стать

Нестыдные вопросы, в том числе — нужно ли уметь рисовать.

easy
Что такое SaaS, IaaS, XaaS и всё остальное
Что такое SaaS, IaaS, XaaS и всё остальное

И сорт авокадо Хаас

easy
Что такое mobile first
Что такое mobile first

Это когда сначала всё делают для смартфона, а потом для всего остального.

easy
Как прокачать буфер обмена
Как прокачать буфер обмена

И что это вообще такое

easy
Что такое low-code и чем он отличается от зеро-кода
Что такое low-code и чем он отличается от зеро-кода

Это когда можно не только мышкой, но и в код залезть

easy
Что такое бэкап, зачем он нужен и как его делать
Что такое бэкап, зачем он нужен и как его делать

Каждый разработчик однажды ЛИШИЛСЯ ВСЕГО, потому что не сделал бэкап.

easy
Кодирование и шифрование — в чём разница?
Кодирование и шифрование — в чём разница?

Одно делается для удобства, а другое — для защиты.

easy
medium