std::vectorの多重配列とイテレータ

今回はC++標準の便利な動的配列であるvectorを多重にしたときの使い方、またそのイテレータをどう使うかをノリで実験して分かってきたのでまとめます。

ちなみに、今回もROOTのインタプリタを使用して実験したのですが、ROOT5のCINTでは多重配列を作るときに何故かエラーが出てテスト出来なかったので、ROOT6のClingを使用しました。普通にGCCを使えばなんの問題もないと思います。

このPostでは簡単のため、二重配列に限って話をします(あとはvectorをどんどんと入れ子にしていけば良いので)

配列の定義

int型の二重配列の定義,iteratorは

std::vector<std::vector<int>> vv;

などと、通常は配列型を入れるところに更にvectorを突っ込むというわりとシンプルな定義です。 ただし、C++11以前のコンパイラでは、誠にご丁寧に

 error: ‘>>’ should be ‘> >’ within a nested template argument list
 std::vector<std::vector<int>> vv;

というエラーが出てコンパイル出来ないので、そのとおりに>>の間に空白を入れましょう。イテレータも従来のように、(vectorの型) ::iteratorと定義すればOKです。

値を詰める

これでvv

vv[0][0], vv[0][1], ..., vv[0][n], vv[1][0], ..., vv[1][n], ..., vv[m][0], ..., vv[m][n]

という感じのm*nの配列が出来ます。もちろん、このm,nは動的(独立)に変更可能です。 ただ、従来のvectorのように値を詰めるときにvv.push_back(0)とすると駄目です。 二重配列では定義式を見る通り、std::vectorを更にvectorにしているので

vv[0].push_back(0);

のように、1番目の添字を指定して、その要素の後に値を追加するということをしなければなりません。 でもその前に、1番目の添字はどうやって作るんだい?というと

vv.resize(1);

と、vectorを詰めているvectorとしてresizeしてやることで作れます。vectorは動的に配列をresizeすることも出来ますので

for (int i = 0; i != 10; ++i) {
	vv.resize(i+1);
	for (int j = 0; j != 5; ++j) {
		vv[i].push_back(10*i+j);
		}
	}

なんて、2つの添字を自由に作ることも可能です。ここで

vv[3].push_back(35);

などとするのもOKで、1つ目の添字に対するvectorのサイズが合っていなくても大丈夫です。

値を読む

詰まっている値を読むには、いつものようにvv[i][j]またはvv.at(i).at(j)などとすれば良いです。また、例えば1番めの添字iのvectorを抜き出した配列を作りたいときには

std::vector<int> v3;
v3 = vv[3];

とすれば取り出せてしまいます。1つ目の要素はvectorの配列なので。あとはよく使うsize()関数も

vv.size(); //第一添字に対するsizeか返ってくる
vv[i].size(); //第一添字iの中のvectorのsizeが返ってくる

とシームレスに使えます。うん、便利!

イテレータについて

多重配列でのイテレータは通常の配列とは違い少々複雑ですが、使い方は同じです。 上の例での二重配列vvに対するイテレータは

std::vector<std::vector<int>> ::iterator itr2;

と定義して

itr2 = v.begin();

などとすれば良いです。しかし、この要素*itr2は当然ながらvectorです。そこが少々複雑です。 C++インタプリタのClingだと文を叩きながら答えを出力してくれるので楽しいです。

root [] std::vector<std::vector<int>> ::iterator itr2;
root [] itr2 = v.begin();
root [] *itr2
(std::vector<int, std::allocator<int> > &) { 0, 1, 2, 3, 4 }
root [] ++itr2;
root [] *itr2
(std::vector<int, std::allocator<int> > &) { 10, 11, 12, 13, 14 }
root [] itr2->at(1)
(int) 11
root [] itr2 += 2;
root [] *itr2
(std::vector<int, std::allocator<int> > &) { 30, 31, 32, 33, 34, 35 }
root [] itr2->size()
(unsigned long) 6
root [] itr2->push_back(36);
root [] *itr2
(std::vector<int, std::allocator<int> > &) { 30, 31, 32, 33, 34, 35, 36 }
root [] *(itr2->begin())
(int) 30

なんて感じで。重要なのはこのiteratorはvvの1番めの要素に対する(つまりvector型の)ポインタになっているということです。そしてね、例えばvv[3]の最初の要素に対するイテレータを作りたいときは

std::vector<int> itr;
itr = vv[3].begin();

もしくは、二重配列に対する先ほど作って、vv[3]を参照しているイテレータitr2を用いて

itr = itr2->begin();

とすればOK。

Shoichiro Masuoka

CNS, the Univ. of Tokyo. Dcotoral student

関連項目