プログラミングを始めたばかりの人が真っ先に引っかかるのは、たいてい「なぜ最初の要素が 0 番目なのか」という疑問である。1 番目でないのが、どうにも腑に落ちない。かくいう僕も、最初は誰かの悪ふざけだと思っていた。
たしかに日常の感覚とはずれている。ところが、コンピュータの世界では、0 から数えることに数学的にも構造的にも強力な利点がある。その理由を、メモリアドレスと計算の美しさという二つの側面からほどいてみたい。悪ふざけにしては、存外よくできているのだ。
距離としてのインデックス
もっとも本質的な理由は、インデックスが「何番目か」ではなく「先頭からどれだけ離れているか」を表している点にある。そもそも配列は、メモリ上に連続して並んだ箱のようなものだ。先頭の箱から何歩進むか、と言い換えてもいい。コンピュータが特定の要素の場所を探すとき、裏側では次のような計算が行われている。
要素のアドレス = 先頭のアドレス + (インデックス × 要素のサイズ)
もし配列が 1 から始まるとしたら、この計算は毎回こうなる。
要素のアドレス = 先頭のアドレス + ((インデックス - 1) × 要素のサイズ)
つまり、要素にアクセスするたびに律儀に 1 を引かなければならない。一歩も歩いていない場所を「1 番目」と呼ぶせいで、毎回つじつま合わせが要るわけだ。コンピュータにとっては純然たる無駄であり、0 から始めれば「先頭のアドレス + 距離」という素朴な足し算で済む。インデックス 0 とは、先頭から 0 歩という意味に過ぎないのだ。
範囲の書きやすさ
次に、範囲の表現がすっきりする。0 始まりの配列で長さn の範囲を書くと、インデックスは 0 <= i < n となる。スライスのような範囲は [start, end) と書ける。start を含み end を含まない、いわゆる半開区間である。
こうしておくと、要素数は end - start という単純な引き算で求まる。start から end まで何歩あるかを、そのまま数えているだけだ。一方、1 始まりだと範囲は 1 <= i <= n となる。引き算のあとに + 1 をしないと、要素数が合わない。
アルゴリズムを書く際、この「プラス 1」の有無はオフバイワンエラーの温床になりやすい。たかが 1 と高を括った者から順に、ループの端で転ぶ。その意味でも、0 始まりの方が安全なのである。
C 言語からの継承
最後に、歴史的な事情がある。Java、Python、JavaScript など、現代の多くの言語は 0 始まりだ。C 言語の設計を色濃く受け継いでいるからである。
C 言語において、配列の添字a[i] はポインタ演算 *(a + i) の書き換えでしかない。いわゆるシンタックスシュガーだ。ポインタに足す値は、そのまま先頭からのオフセット、すなわち歩数を意味していた。となれば、0 始まりは必然である。
しかも、この設計はあまりに合理的だった。後発の言語もそれに倣い、いつしかデファクトスタンダードになった。悪ふざけどころか、由緒正しい作法だったわけだ。
以上、配列のインデックスが 0 から始まる理由を、距離、範囲計算、C 言語からの継承という三つの観点から眺めた。1 から数えるのは人間にとって自然だが、0 からオフセットを計算するのはコンピュータにとって自然である。array[0] は、先頭から一歩も動いていない場所をまっすぐ指しているに過ぎない。数え方の違いは、立場の違いなのだ。