Чому індексація починається з нуля в "C"?


154

Чому індексація в масиві починається з нуля в C, а не з 1?


7
Це все про покажчики!
медопал


3
Вказівник (масив) - це напрямок пам'яті, а індекс - це зміщення цього напрямку пам’яті, тому першим елементом вказівника (масиву) є той, який зміщений, дорівнює 0.
D33pN16h7,

3
@drhirsch тому, що коли ми підраховуємо набір об’єктів, ми починаємо, вказуючи на об’єкт і вимовляючи "один".
фог

1
Американці рахують поверхи (поверхи) будівлі від одного на першому поверсі; Британці рахують з нуля (перший поверх), рухаючись до першого поверху, потім другого поверху тощо.
Джонатан Леффлер

Відповіді:


116

У C ім'я масиву є по суті вказівником [але див. Коментарі] , посиланням на місце пам'яті і тому вираз array[n]посилається на nелементи розташування пам’яті подалі від початкового елемента. Це означає, що індекс використовується як зміщення. Перший елемент масиву точно міститься в місці пам'яті, на який посилається масив (0 елементів), тому його слід позначати як array[0].

Для отримання додаткової інформації:

http://developeronline.blogspot.com/2008/04/why-array-index-should-start-from-0.html


20
Ім'я масиву - це ім'я масиву; всупереч поширеній омані, масиви не є покажчиками в жодному сенсі. Вираз масиву (наприклад, ім'я об’єкта масиву) зазвичай, але не завжди , перетворюється в покажчик на перший елемент. Приклад: sizeof arrдає розмір об’єкта масиву, а не розмір вказівника.
Кіт Томпсон

Хоча ви, очевидно, не відреагували на коментар @ KeithThompson, я хотів би, щоб ви скористалися більш кривдним курсом: " У C ім'я масиву по суті є вказівником, посиланням на місце пам'яті " - Ні, це не так . Принаймні, не з загальної точки зору. Незважаючи на те, що ваша відповідь ідеально відповідає на питання таким чином, наскільки важливим є старт 0 як індекс, перше речення є явно невірним. Масив не завжди розпадається на вказівник на його перший елемент.
RobertS підтримує Моніку Селліо

Цитата зі стандарту C, (C18), 6.3.2.1/4: " За винятком випадків, коли це операнд sizeofоператора або унарний &оператор, або це рядковий літерал, який використовується для ініціалізації масиву, вираз, що має тип" масив типу "перетворюється на вираз з типом" вказівник на тип ", який вказує на початковий елемент об'єкта масиву і не є значенням. Якщо об'єкт масиву має клас зберігання в регістрі, поведінка не визначена. "
RobertS підтримує Monica Cellio

Також цей занепад відбувається більш "неявно" або "формально", ніж пропонується тут; в пам'яті немає об'єкта вказівника. Це об’єкт цього питання: Чи змінюється масив розпаду вказівника на об’єкт вказівника? - Відредагуйте свою відповідь, щоб вона була повністю правильною.
RobertS підтримує Моніку Селліо

103

Це питання було розміщено більше року тому, але ось ...


Про вищезазначені причини

Хоча стаття Dijkstra (раніше згадувана у видаленій відповіді ) має сенс з математичної точки зору, вона не так актуальна, що стосується програмування.

Рішення, яке приймають мовні специфікації та розробники-компілятори, ґрунтується на рішенні, прийнятому дизайнерами комп'ютерних систем, починати рахувати з 0.


Ймовірна причина

Цитуючи Заклик до миру Данні Коена.

Для будь-якої основи b перші b ^ N невід’ємні цілі числа представлені точно N цифрами (включаючи провідні нулі), лише якщо нумерація починається з 0.

Це можна перевірити досить легко. У базі-2 візьміть 2^3 = 8 восьме число:

  • 8 (двійковий: 1000), якщо ми почнемо рахувати з 1
  • 7 (двійковий: 111), якщо ми почнемо рахувати з 0

111може бути представлений за допомогою 3бітів, при цьому 1000буде потрібно додатковий біт (4 біта).


Чому це актуально

Адреси пам'яті комп'ютера мають 2^Nкомірки, адресовані Nбітами. Тепер, якщо ми почнемо рахувати з 1, 2^Nклітинкам знадобляться N+1рядки адреси. Додатковий біт потрібен для доступу до точно 1 адреси. ( 1000у наведеному випадку.). Ще один спосіб вирішити це - залишити останню адресу недоступною та використовувати Nадресні рядки.

Обидва є неоптимальними рішеннями , порівняно з початковим підрахунком на 0, який би підтримував доступ усіх адрес, використовуючи точно Nадреси адреси!


Висновок

Рішення почати рахувати з 0цього часу пронизало всі цифрові системи , включаючи програмне забезпечення, що працює на них, оскільки це спрощує переклад коду на те, що може інтерпретувати базова система. Якби це не було, була б одна непотрібна операція перекладу між машиною та програмістом для кожного доступу до масиву. Це полегшує компіляцію.


Цитування з паперу:

введіть тут опис зображення


2
Що робити, якщо вони щойно видалили шматочок 0 .. тоді восьме число все одно буде 111 ...
DanMatlin

2
Ви насправді пропонуєте змінити базову арифметику, щоб вона вписалась? Ви не вважаєте, що сьогодні є набагато кращим рішенням?
Аніруд Рамананат

Роки пізніше мій 2 цнт вартий. З мого досвіду (~ 35 років програмування), модуль чи модульна операція додавання в тій чи іншій формі виникає напрочуд часто. З нульовою базою наступна послідовність - (i + 1)% n, але з базою 1 приходить (i-1)% n) +1, тому я вважаю, що перевагу на основі 0. Це розвивається в математиці та програмуванні досить часто. Можливо, це лише я або поле, в якому я працюю.
nyholku

Хоча всі вагомі причини, я вважаю, що це набагато простіше: a[b]реалізовувалося, як і *(a+b)в ранніх компіляторах. Навіть сьогодні ви можете все-таки писати 2[a]замість a[2]. Тепер, якщо індекси не почалися з 0, то a[b]перетворилися б на *(a+b-1). Для цього було б потрібно 2 доповнення на процесорі того часу замість 0, що означає половину швидкості. Явно не бажано.
Госвін фон Бредерлоу

1
Тільки тому, що ви хочете 8 станів, це не означає, що ви повинні мати в них число 8. Вимикачі світла в моєму будинку із задоволенням представляють стан "увімкнено світло", "вимкнено світло", не замислюючись, чому вони не представляють число 2.
Spyryto

27

Тому що 0 - наскільки далеко від вказівника до голови масиву до першого елемента масиву.

Поміркуйте:

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

Для доступу до 0 ми робимо:

foo[0] 

Але foo розкладається на покажчик, і вищевказаний доступ має аналогічний арифметичний спосіб вказівника доступу до нього

*(foo + 0)

У наші дні арифметика вказівника використовується не так часто. Зворотній шлях, хоча, це був зручний спосіб взяти адресу і перемістити X "ints" від цієї вихідної точки. Звичайно, якщо ви хотіли просто залишитися там, де ви є, ви просто додасте 0!


23

Тому що індекс на основі 0 дозволяє ...

array[index]

... реалізується як ...

*(array + index)

Якби індекс базувався на 1, компілятору потрібно було б генерувати:, *(array + index - 1)і це "-1" зашкодило б продуктивності.


4
Ви піднімаєте цікавий момент. Це може зашкодити продуктивності. Але чи вдалий показник буде виправдовувати використання 0 в якості стартового індексу? Я сумніваюся в цьому.
FirstName LastName

3
@FirstNameLastName 1-базисні індекси не пропонують переваги перед 0-базисними індексами, але вони працюють (трохи) гірше. Це виправдовує 0-базисні індекси, незалежно від того, наскільки "малий" коефіцієнт посилення. Навіть якщо індекси на основі 1 пропонують певну перевагу, вибирати продуктивність над зручністю варто в дусі C ++. C ++ іноді використовується в контекстах, де важливий останній шматочок продуктивності, і ці "маленькі" речі можуть швидко скластися.
Бранко Димитріевич

Так, я розумію, що дрібні речі можуть складатись і часом ставати великими. Наприклад, 1 долар на рік - це не багато грошей. Але якщо 2 мільярди людей пожертвують, то ми можемо зробити багато корисного для людства. Я шукаю подібний приклад кодування, який може спричинити низьку продуктивність.
FirstName LastName

2
Замість того, щоб віднімати 1, ви повинні використовувати адресу масиву-1 в якості базової адреси. Це те, що ми робили в компіляторі, над яким я колись працював. Це виключає віднімання часу виконання. Коли ви пишете компілятор, ці додаткові інструкції мають велике значення. Компілятор буде використовуватися для генерації тисяч програм, кожна з яких може бути використана тисячі разів, а додаткова 1 інструкція може з'являтися в декількох рядках всередині n-квадратичного циклу. Це може скласти до мільярдів витрачених циклів.
progrmr

Ні, це не завдасть шкоди продуктивності, коли вона буде складена, це додасть лише невеликий час збірки, оскільки в підсумку це буде переведено на машинний код. Це тільки зашкодить дизайнерам-компіляторам.
Хассан Акбар

12

Тому що це спростило компілятор і лінкер (простіше писати).

Довідка :

"... Посилальна пам'ять за адресою та зміщенням представлена ​​безпосередньо в апаратних засобах практично в усіх архітектурах комп'ютерів, тому ця детальна конструкція в C полегшує компіляцію"

і

"... це робить простішою реалізацію ..."


1
+1 Не впевнений, чому голоси проти. Хоча це не відповідає безпосередньо на питання, індексація на основі 0 не є природною для людей чи математиків - єдина причина, що це робиться, це те, що реалізація є логічно послідовною (простою).
phkahler

4
@phkahler: помилка в авторах та мовах, які називають масиви індексами як індекси; якщо ви вважаєте це як компенсацію, то базування на 0 стає природним і для мирян. Розгляньте годинник, перша хвилина написана як 00:00, а не 00:01 чи не так?
Lie Lie Ryan

3
+1 - це, мабуть, найправильніша відповідь. Попередній документ Djikistras і був однією з найбільш ранніх мов "start at 0". C почав життя "як асемблер високого рівня", і цілком ймовірно, що K&R захотів дотримуватися того самого способу, як це було зроблено в асемблері, де ви, як правило, маєте базову адресу плюс зміщення, починаючи з нуля.
Джеймс Андерсон

Я подумав, що питання було використано на основі 0, а не краще.
progrmr

2
Я не підкреслюю, але, як прогрмр коментується над базою, можна потурбуватися, регулюючи адресу масивів, так що незалежно від часу виконання бази однаковий, і це тривіально для реалізації в компіляторі чи інтерпретаторі, так що насправді це не робить простішою реалізацію . Свідок Паскаль, де ви можете використовувати будь-який діапазон для індексації IIRC, минуло 25 років;)
nyholku

5

Індекс масиву завжди починається з нуля. Припустимо, базова адреса - 2000. Тепер arr[i] = *(arr+i). Тепер if i= 0це означає *(2000+0) дорівнює базовій адресі або адресі першого елемента в масиві. цей індекс трактується як зсув, тому індекс за замовчуванням починається від нуля.


5

З тієї ж причини, що коли середа, і хтось запитує вас, скільки днів до середи, ви кажете 0, а не 1, а коли середа, а хтось запитує, скільки днів до четверга, ви кажете 1, а не 2.


6
Ваша відповідь здається лише питанням думки.
heltonbiker

6
Ну, це те, що змушує додавати індекси / компенсації. Наприклад, якщо "сьогодні" дорівнює 0, а "завтра" - 1, "завтра" - 1 + 1 = 2. Але якщо "сьогодні" - 1, а "завтра" - 2, "завтра" - не 2 + 2. У масивах це явище трапляється всякий раз, коли ви хочете вважати піддіапазон масиву як масив самостійно.
R .. GitHub ЗАСТОСУЄТЬСЯ ДОПОМОГАТИ

7
Називати колекцію з 3 речей "3 речі" та нумерувати їх 1,2,3 - це не недолік. Нумерація їх із зміщенням від першого не є природною навіть у математиці. Єдиний час, коли ви індексуєте нуль в математиці, це коли ви хочете включити щось, як нульова потужність (постійний термін), в поліном.
phkahler

9
Re: "Нумерація масивів, що починається з 1, а не з 0, призначена для людей з важким дефіцитом математичного мислення". У моєму виданні CLR "Вступ до алгоритмів" використовується індексація масиву на основі 1; Я не думаю, що автори мають дефіцит у математичному мисленні.
RexE

Ні, я б сказав, що сьомий знаходиться в індексі 6, або в 6 позиціях від першого.
R .. GitHub СТОП ДОПОМОГАЙТЕ

2

Найбільш елегантне пояснення, яке я читав для нумерації на нульовому рівні, - це спостереження, що значення зберігаються не в позначених місцях рядка чисел, а в проміжках між ними. Перший елемент зберігається між нулем і одним, наступний між одним і двома і т. Д. N-й елемент зберігається між N-1 і N. Набір елементів може бути описаний за допомогою цифр з обох сторін. Індивідуальні предмети описуються за умовами, використовуючи числа під ним. Якщо одному задано діапазон (X, Y), ідентифікація окремих чисел за допомогою наведеного нижче числа означає, що можна ідентифікувати перший елемент без використання арифметики (це елемент X), але треба відняти один з Y для ідентифікації останнього елемента (Y -1). Визначення елементів за допомогою наведеного вище числа полегшило б ідентифікацію останнього елемента в діапазоні (це було б пункт Y),

Хоча не було б жахливо ідентифікувати предмети на основі числа над ними, визначення першого елемента в діапазоні (X, Y) як першого вище X, як правило, виходить більш приємним, ніж визначення його як нижнього (X + 1).


1

Технічна причина може випливати з того, що вказівник на місце пам'яті масиву - це вміст першого елемента масиву. Якщо ви декларуєте покажчик з індексом одиниці, програми зазвичай додають це значення до вказівника, щоб отримати доступ до контенту, який, звичайно, не є бажаним.


1

Спробуйте отримати доступ до піксельного екрану за допомогою координат X, Y на матриці на основі 1. Формула надзвичайно складна. Чому складний? Тому що ви перетворюєте координати X, Y в одне число, зміщення. Чому вам потрібно перетворити X, Y на зміщення? Тому що так організована пам'ять всередині комп'ютерів, як безперервний потік комірок пам'яті (масивів). Як комп’ютери мають справу з клітинками масиву? Використання зрушень (переміщення з першої комірки, нульова модель індексації).

Тож у якийсь момент коду, який вам потрібен (або потрібен компілятор), перетворити формулу 1-бази в формулу, засновану на 0, тому що так комп’ютери мають справу з пам'яттю.


1

Припустимо, ми хочемо створити масив розміром 5
int масиву [5] = [2,3,5,9,8],

нехай перший елемент масиву буде вказаний на розташування 100,

і ми вважатимемо, що індексація починається з 1, а не з 0.

тепер ми повинні знайти розташування 1-го елемента за допомогою індексу
(пам’ятаємо, що розташування 1-го елемента є 100),

оскільки розмір цілого числа є 4-розрядним,
тому -> враховуючи індекс 1, позиція буде
розміром індексу (1) * розмір цілого числа (4) = 4,
тому фактичне положення, яке воно нам покаже, становить

100 + 4 = 104

що не відповідає дійсності, оскільки початкове розташування було 100.
воно повинно вказувати на 100, а не на 104,
це неправильно,

припустимо, ми взяли індексацію з 0,
тоді
позиція 1-го елемента повинна бути
розміром індексу (0) * розмір цілого числа (4) = 0,

тому ->
розташування 1-го елемента становить 100 + 0 = 100,

і це було фактичне розташування елемента
, тому індексація починається з 0;

Сподіваюся, це зрозуміє вашу точку зору.


1

Я з яванського походження. Я представив відповідь на це питання на діаграмі, нижче якої я написав на аркуші паперу, який сам пояснює

Основні кроки:

  1. Створення довідника
  2. Моментальність масиву
  3. Виділення даних масиву

  • Також зверніть увагу, коли масив просто створений .... Нуль розподіляється на всі блоки за замовчуванням, поки ми не призначимо його значення
  • Масив починається з нуля, оскільки перша адреса буде вказувати на посилання (i: e - X102 + 0 на зображенні)

введіть тут опис зображення

Примітка : Блоки, показані на зображенні, є зображенням пам'яті


0

Перш за все вам потрібно знати, що масиви внутрішньо розглядаються як покажчики, оскільки "саме ім'я масиву містить адресу першого елемента масиву"

ex. int arr[2] = {5,4};

врахуйте, що масив починається з адреси 100, тому перший елемент елемента буде на адресу 100, а другий - на 104, врахуйте, що якщо індекс масиву починається з 1, тож

arr[1]:-

це можна записати у виразі покажчиків, як

 arr[1] = *(arr + 1 * (size of single element of array));

врахуйте, що розмір int становить 4 байти,

arr[1] = *(arr + 1 * (4) );
arr[1] = *(arr + 4);

як ми знаємо, ім'я масиву містить адресу його першого елемента, так arr = 100 зараз,

arr[1] = *(100 + 4);
arr[1] = *(104);

що дає,

arr[1] = 4;

через це вираження ми не можемо отримати доступ до елемента за адресою 100, який є першим офіційним елементом,

тепер розглянемо індекс масиву починається з 0, так

arr[0]:-

це буде вирішено як

arr[0] = *(arr + 0 + (size of type of array));
arr[0] = *(arr + 0 * 4);
arr[0] = *(arr + 0);
arr[0] = *(arr);

тепер ми знаємо, що ім'я масиву містить адресу його першого елемента,

arr[0] = *(100);

що дає правильний результат

arr[0] = 5;

тому індекс масиву завжди починається від 0 у c.

довідково: всі деталі написані в книзі "Мова програмування C Брайаном Кернінгханом та Деннісом Рітчі"


0

У масиві індекс вказує відстань від початкового елемента. Отже, перший елемент знаходиться на відстані 0 від стартового елемента. Отже, тому масив починається з 0.


0

Це тому, що addressмає вказувати право elementна масив. Припустимо, наведений нижче масив:

let arr = [10, 20, 40, 60]; 

Розглянемо тепер початок буття адреси 12та розмір elementбукви 4 bytes.

address of arr[0] = 12 + (0 * 4) => 12
address of arr[1] = 12 + (1 * 4) => 16
address of arr[2] = 12 + (2 * 4) => 20
address of arr[3] = 12 + (3 * 4) => 24

Якби цього не було zero-based, технічно нашим першим елементом елемента в arrayби було те, 16що неправильно, як це його місцезнаходження 12.


-2

Ім'я масиву - це постійний вказівник, що вказує на базову адресу. Коли ви використовуєте arr [i], компілятор маніпулює нею як * (arr + i). Діапазон int - від -128 до 127, компілятор вважає, що від -128 до -1 від’ємні числа та 0 до 128 - додатні числа. Отже, індекс масиву завжди починається з нуля.


1
Що ви маєте на увазі під "int-діапазоном від -128 до 127" ? intТипу потрібно для підтримки щонайменше , 16-бітний діапазон, і на більшості систем ці дні підтримує 32 біта. Я думаю, що ваша логіка є хибною, і ваша відповідь справді не покращується щодо інших відповідей, які вже надали інші люди. Я пропоную видалити це.
Джонатан Леффлер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.