Когда вы читаете, вы начинаете с ABC, когда вы поете, вы начинаете с До-Ре-Ми, когда вы начинаете массив, он начинается с 0 1 2 3.

Если вы используете языки высокого уровня, такие как Java, Python или JavaScript, может быть нелегко понять, как массивы работают под капотом, потому что эти языки абстрагируются от большой сложности. Одна из причин - почему 1-й элемент в массиве имеет индекс 0, а не 1.

Чтобы понять, давайте посмотрим, как массивы работают в C.

Чтобы инициализировать массив:

int a[5] = {1,2,3,4,5};

Теперь у нас есть массив из 5 пустых элементов целочисленного типа от индекса 0 до 4.

Когда мы запускаем программу, ОС выделяет часть памяти. Допустим, в нашей программе больше логики, чем просто массив, а выделенная память составляет 64 КБ. Математически равно 64 * 1024 = 65536 байт.

Итак, наша память выглядит так:

Допустим, у нас есть еще одна переменная i:

int i = 30;

Обычно размер целого числа составляет 4 байта. ОС выделяет непрерывные 4 байта памяти для представления этого значения. Теперь предположим, что i хранится в байте с номером 100, который представляет собой не что иное, как его адрес.

Теперь возвращаясь к нашему массиву, предположим, что массив хранится по адресу 3000.

Теперь, если бы мы просто напечатали a, мы бы увидели кое-что интересное:

printf(“%d”,a);
// => 3000

Здесь мы получаем адрес массива «a».

Если бы мы попробовали аналогичный подход с использованием JS, это не сработало бы. Вместо этого возвращается массив:

var a = [1,2,3,4,5]
console.log(a)
// => [1,2,3,4,5]

Если вы немного знаете C, то у нас есть концепция указателей ‘*’. Проще говоря, указатели возвращают значение по определенному адресу памяти.

Итак, мы знаем, что a - это адрес массива, поэтому, чтобы получить значение по этому адресу, мы используем указатель, показанный ниже:

printf(“%d”,*a);
// => 1

Мы получаем значение 1, которое является 1-м значением в массиве.

Это означает * a = a [0];

printf(“%d %d”,*a, a[0] );
// => 1 1

Но почему 0?

Что ж, давайте посмотрим, что происходит, когда мы добавляем к адресу целое число,

a = 3000

a + 1 = 3004

a + 2 = 3008

Почему 3000 + 1 = 3004? Потому что помните, что мы сохранили целочисленные значения в нашем массиве, и каждое целочисленное значение имеет 4 байта. Добавление значений в адреса - это не просто добавление. Под добавлением 1 мы подразумеваем добавление 1 блока размером целого числа. Когда мы добавляем 2, мы добавляем 2 целочисленных блока = 3000 + (2 * 4) = 3008 и так далее.

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

printf(“%d”,*a);
printf(“%d”,*(a+1));
printf(“%d”,*(a+2));
// => 1 2 3

это первые 3 значения массива. Это похоже на то, как мы обращаемся к массивам с помощью оператора индекса ([]).

Оператор [] принимает 2 аргумента: имя переменной и значение индекса. Например, [1], который выполняет * (a + 1) под капотом.

a [1] означает добавление 1 (4 байта, если целое число) к адресу a и доступ к значению с помощью указателя.

И наконец, зачем начинать с индекса 0?

Поскольку [0] станет * (a + 0), что равно * a = 1, то есть 1-му значению массива, которое совпадает со значением в индексе 0.

printf(“%d”,*a);
printf(“%d”,*(a+0));
// => 1 1

Еще одна интересная вещь, на которую стоит обратить внимание; поскольку оператор [] добавляет адрес и индекс и учитывает его (т. е. используя указатель) для получения значения, мы можем переключать аргументы, а также:

printf(“%d”,0[a]);
// => 1

который также дает значение с индексом 0.

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

Примечание: не все языки имеют индекс массива от 0. Примеры: Fortran, R, Julia, MATLAB и т. д.