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
vv[0].push_back(0);
のように、1番目の添字を指定して、その要素の後に値を追加するということをしなければなりません。 でもその前に、1番目の添字はどうやって作るんだい?というと
vv.resize(1);
と、vector
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つ目の要素はvectorsize()
関数も
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。