ループにおける配列のポインタ

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
トロンプルイユとも言いますな

この記事を三行にまとめると

ポインタの挙動について検証してみたい
whileを使った方が良いみたいですね
何でforeachはポインタが進まないんだろ
PHP(他の言語も一緒かもですが)では、配列のポインタという概念があります。配列の何番目を指しているか、みたいな感じですね。

current()という関数を使うと、ポインタが今どこを指しているかを確かめることができます。

$a = array(1,2,3,4,5,6,7,8,9,10);
$current = current($a);
echo $current;

//出力結果
1

こんな感じです。「1」が出力されたということは、ポインタが配列の先頭の要素を指していることになります。

このポインタを自分で進めたり戻したりするには、next()やprev()という関数を使います。

$a = array(1,2,3,4,5,6,7,8,9,10);

$current = current($a);
echo $current;

$next = next($a);
echo $next;

$prev = prev($a);
echo $prev;

//出力結果
1
2
1

next()によってポインタが配列の二番目に移動し、prev()で一番目に戻っています。

今回は、foreach()やwhile()というループ処理における、このポインタの挙動について、検証してみたいと思います。

ちなみに今回検証したPHPのバージョンは5.4です。バージョンによって挙動が違うかもしれないので、念のため。



foreach中のポインタ

foreachの中で配列のポインタがどうなっているか、ちょっと見てみましょう。

$a = array(1,2,3,4,5,6,7,8,9,10);
foreach($a as $b) {
  $current = current($a);
  echo $current;
}

//出力結果
2
2
2
2
2
2
2
2
2
2

やってみれば分かるんですが、ずっと配列の二番目を指しています。ずっと「2」が出力され続けている状態です。

正直、この挙動がよく分かりません。

foreachのマニュアルを見ると、こんなことが書いてあるんですよ。

PHP5では、foreachの実行開始時に内部配列ポインタは、 配列の先頭要素を指すように自動的にリセットされます。 このため、foreachループの前に reset()をコールする必要はありません。
PHP5では、foreachは内部の配列ポインタに依存するので、 ループ内で配列ポインタを変更すると予期せぬ振る舞いを引き起こします。


この注意書きを見る限りだと、ループが始まった段階では、ポインタは配列の先頭の要素を指しているように思います。だから最初のcurrent()では「1」が取得できる気がするんですけどね。ついでに「内部の配列ポインタに依存する」っていう文面からは、ループと同時にポインタも自動的に移動するような印象を受けるんですけど、特にそういうこともなく、ずっと二番目を指したままなんですよね。

ループ内でポインタを変更すると予期せぬ振る舞いをするとも書いてありますが、実際にnext()を使ってポインタを進めるとどうなるか。

foreach($a as $b) {
  $current = current($a);
  echo $current;
  next($a);
}

//出力結果
2
3
4
5
6
7
8
9
10

特に何事もなく、2〜10が順番に出力されています。予期せぬ振る舞いはしていない。

結局のところ、何で最初から二番目を指しているのかはよく分かりませんが、とりあえずforeachの処理中に、ポインタが自動的に次の要素に進んだりはしないみたいです。

ついでに言うと、予期せぬ振る舞いってのがどんな振る舞いなのかも、今回の検証では分かりませんでした。



while中のポインタ

では今度は、while()を使って、今と同じことをやってみたいと思います。

$a = array(1,2,3,4,5,6,7,8,9,10);
while(each($a)) {
  $current = current($a);
  echo $current;
}

//出力結果
2
3
4
5
6
7
8
9
10

whileの場合は、next()を使わなくても、ポインタが自動的に次に進みます。二番目から始まるっていう点は、foreachと同じですね。

こっちの場合は、whileがどうのって言うより、each()が配列のポインタを次に進める処理をするので、ループ開始時に最初のeachでポインタが二番目に移動し、その後にcurrent()を使っているので、二番目から順番に出力されているんですね。だからこの挙動は何の問題もないと思われる。

whileとeachの組み合わせは、ポインタが最後の要素まで進んだ後、次のループでfalseが返ってくることでループが終了するので、うっかり処理中にprev()やらreset()を使ってポインタを前に戻してしまうと、無限ループ処理になってしまいます。注意っす。



ちなみに、array_shiftを使っても、同じ結果になります。

$a = array(1,2,3,4,5,6,7,8,9,10);
while(array_shift($a)) {
  $current = current($a);
  echo $current;
}

この場合はポインタが進むのではなく、ずっと先頭を指しっぱなしですが、array_shiftによって先頭の配列が取り除かれるので、結果的には次の要素を取得できていると。






ということで、もしループ処理中にポインタを進めて次の要素を取得するような処理が必要なときは、foreachよりも、whileプラスeachを使った方が良いみたいですね。そういう処理が必要になることは、めったにない気がしますけど。

それにしても、何でforeachはポインタが進まないんだろ。二番目の要素が出力されるってことは、ループ開始時にポインタが一つ進んでいるってことのような気がするんですけど、それならずっと二番目のままってのも不自然だし……謎です。
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください