多次元配列を考えてみる
C言語の勉強をしているときに、少し引っかかった点。
本によると、以下のような 2 次元配列は:
int array[2][2] = {
{0, 1},
{2, 3}
};
次のように書き換えることができる:
int array[][2] = {0, 1, 2, 3};
最初の添字が省略可能であるという点が異なる。ところで、なぜ省略できるのだろうか・・・
1 次元配列
まず、1 次元配列で考えてみる。1 次元配列でも添字は省略できる。
int array[4] = {0, 1, 2, 3}; /* 省略しないバージョン */
int array[] = {0, 1, 2, 3}; /* 省略したバージョン */
このとき、メモリ内部を図式化すると、以下のようになる。

配列の各要素が連続して並んでいることがわかる。これは、添字を省略した場合も、しなかった場合も同じである。
2 次元配列
では、2 次元配列はどうなっているだろうか。実は、2 次元配列の各要素も、メモリ内部では連続して並んでいる。 1

まず、各要素は連続して並んでいる。この時点では、単なる 1 次元配列であるが、次に、各要素をもっと高いレベルでまとめる。上図であれば、0 と 1 を array[0][...] という配列にまとめている。これが 2 次元配列である。
従って、冒頭の 2 次元配列は:
int array[][2] = {0, 1, 2, 3};
2番目の添字は、どのくらいの要素をより高いレベルでまとめるかを表している。 2これだけで、どのような構造になるかがわかるため、最初の添字は省略できるのである。
多次元配列
さて、以上のことは、他の多次元配列にも当てはまるだろうか。例えば、以下は 3 次元配列である:
int array[2][3][4] = {
{
{1, 2, 3, 4,},
{5, 6, 7, 8,},
{9, 10, 11, 12,}
},
{
{13, 14, 15, 16,},
{17, 18, 19, 20,},
{21, 22, 23, 24}
}
};
ここで省略できる添字はどれかを考える。考えられるのは以下の 2 通りである:
int array[][][4] = {...} /* これを A とする */
int array[][3][4] = {...} /* これを B とする */
ここで思い出すべきなのは、添字はどのくらいの要素をまとめるかを表すものであるということである。
まず、A では、1 次元配列の個数が 4 であることはわかる。しかし、2 次元配列としてどのくらいまとめるべきかわからない。当然、3 次元配列としてどのくらいまとめるべきかもわからない。従って、A は不適切である。
一方、B では、2 次元配列として 3 つずつまとめるべきであることがわかる。そして、自ずと、3 次元配列としての個数も定まる。従って、B が適切である。
ちなみに、『プログラミング言語C』には以下のような記述がある:
これは多次元配列においては、最初の次元のみが省略できることを意味する。 3
この規定からわかるのは、配列が行ごとに格納され(最後の添字が最も速く変化する)、宣言中の最初の添字は配列が消費するメモリ量を決めるのには役立つが、添字の計算には関係しないということである。 4
まとめ
- メモリ上では配列の各要素は連続して並んでいる
- 配列の添字はどのくらいの要素をまとめるかを表すための物
- 最初の添字のみ省略可能である
foreach 構文には気をつけよう
「PHP5技術者認定初級試験を受けてきました」という記事を読んでいて 1、以下の部分が気になった:
<?php
$hoge = array();
$hoge[3] = 'a';
$hoge[2] = 'b';
$hoge[0] = 'c';
$hoge[1] = 'd';
foreach ($hoge as $val) {
echo $val;
}
これを実行すると、「abcd」になる。
と、書いてあったけど、なんでだろう?
最初、PHP は配列の添字を完全に無視しているのかと疑ったが、
print $hoge[0];
と実行すると、「c」が表示される。 2
そこで、次は foreach 構文を疑ってみた。すると、添字をまったく指定していないことに気づく。添字を指定できるループといえば for 構文なのでやってみる:
for ($i = 0; $i < count($hoge); $i++) {
print $hoge[$i];
}
すると、「cdba」と表示される。やはり、foreach 構文が怪しいようだ。
そこで、ふと、『初めてのPHP5』 3を開くと、以下のような記述があった(p56):
foreach() を使って配列内を反復するときに、要素は配列に追加される順番でアクセスされます。(中略)キーが普通に整列されるやり方とは異なる順番で追加される数値配列にすると、予期しない結果になります。
つまり、foreach 構文では、添字は事実上無視されているらしい。数値配列を出力させたいときは、for 構文を使うのが最善のようだ。
ちなみに、PHP の配列は、マニュアル を見てわかるとおり、数値配列と連想配列が同一 4とされている。たしかに便利だけど、そういうところが嫌われる要因なんだろうなあ・・・
プリペアドステートメントではまった
PDO + SQLite でデータベース(蔵書管理)を作っていたが、プリペアドステートメントではまってしまった。これに 1 日悩んだ。恥ずかしい・・・(汗
正誤
誤
$sth = $conn->prepare("SELECT title FROM books WHERE author LIKE '%?%'");
$sth->bindValue(1, '田中');
$sth->execute();
while ($row = $sth->fetch()) {
print "<p>" . $row['title'] . "</p>";
}
正
$sth = $conn->prepare("SELECT title FROM books WHERE author LIKE ?");
$sth->bindValue(1, '%田中%');
$sth->execute();
while ($row = $sth->fetch()) {
print "<p>" . $row['title'] . "</p>";
}
問題点
問題は、LIKE 以下の ? の使い方。誤のように、「'%?%'」としてしまうと、結果が返ってこない。正のように、bindValue の引数にワイルドカード(%)も入れないといけない。
PHP マニュアル によると:
ということらしい。