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

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

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

В проектах мы периодически говорим, что компьютер почти всё начинает считать с нуля, а не с единицы. Это значит, что первый элемент массива вызывается командой 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