ストラウストラップのプログラミング入門(35) 20章「コンテナとイテレータ」
『ストラウストラップのプログラミング入門』を読む。今日は20章です。STLが登場しました。
20章は、「STLに用意された様々なコンテナの一端を知ること」と、「なぜ様々なコンテナが用意されているかを理解すること」の2つがテーマです。(たぶん・・・)
コンテナ
コンテナの例として、vectorとlistが挙げられ、その特徴が解説されました。特に、listについては、LinkedListの利点である、コンテナの途中への要素の追加が低コストであることがフォーカスされていました。
が、結論としては、特に理由がなければvetorを使えばいいようです。
STL
STLは一般化された解決策を用意し、ユーザは一貫性のある操作でそれを利用できます。ひいては、コードを書く人間が考えることの量を減らしてくれるともいえます。素晴らしいですね。
しかし、要は規約ですので、慣習を覚えるまでは、恩恵に気づかなさそうです。知識がなければ、一般化する発想ができず、発想がなければ、規約を踏まえたものを作ることもできません。
そんな話を内輪でしたところ、「人様の作ったものを使っているうちに覚えるんじゃない?」という話になりました。そりゃそうですね。
20章ドリル
ドリルでは、int型の組込みの配列、vetor
ドリル6、7: iteratorを規約とする関数で、配列も他のコンテナも同様に扱う
まず、ドリル6では、[f1, e1) を [f2, f2+(e1-f1)) にコピーして、f2+(e1-f1) を返す関数を作ります。
template<class Iter1, class Iter2> Iter2 copy0(Iter1 f1, Iter1 e1, Iter2 f2) { if(f1 == e1) { return f2; } Iter2 cmp = f2 + (e1 - f1); for(Iter1 ite = f1; ite != e1; ++ite){ *f2 = *ite; if(f2 != cmp){ ++f2; } else { break; } } return f2; }
(・・・こうかな?)
次に、ドリル7で、「配列をvectorに、listを配列にコピーしろ」といわれます。
イテレータを渡す関数のつもりで作っていたら、配列とは、、、。
しかし、配列要素の先頭と末尾の1つの後のアドレスを渡すと、問題なく動きました。
int main() { int ai[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; vector<int> vi(10); for(int i = 0; i < 10; ++i){ vi[i] = 3 + i; } list<int> li(0); for(int i = 0; i < 10; ++i){ li.push_back(5 + i); } copy(&ai[0], &ai[10], vi.begin()); // ★コレ copy(li.begin(), li.end(), &ai[0]); }
範囲外の要素を引数に渡すのは違和感がありますが、イテレータと使い勝手が同じなのはベンリです。これでいいのだろうか。。。 (追記: 2012/01/26)C言語の規格上、この使い方で問題ないそうです。E_Mattsanさんに教えていただきました。(規格引用+解説はコメント欄を参照ください)
ドリル8: vectorとlistをiteratorで扱い、添え字を取り出す
vectorは、戻り値のiteratorから、vector
int main() { // { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 } の vector<int> を作成 vector<int> hoge(10); for(int i = 0; i < hoge.size(); ++i){ hoge[i] = i + 3; } int t1 = 3; vector<int>::iterator hoge_ite = find(hoge.begin(), hoge.end(), t1); // 位置を表示 if(hoge_ite == hoge.end()){ cout << t1 << " is not found." << endl; } else { cout << t1 << " is at " << hoge_ite - hoge.begin() << endl; } }
しかし、listでは同じことができないので、困りました。
仕方がないので、begin()と同じ値になるまで、iteratorを減じながらカウントしました。
int main() { // { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 } の list<int>を用意 list<int> moga(0); for(int i = 0; i < 10; ++i){ moga.push_back(i + 5); } int t2 = 27; list<int>::iterator moga_ite = find(moga.begin(), moga.end(), t2); // 位置を表示 if(moga_ite == moga.end()){ cout << t2 << " is not found." << endl; } else { /* ☆distanceを使うことにしたため、削除 int c = 0; while(moga_ite != moga.begin()){ --moga_ite; ++c; } cout << t2 << " is at " << c << endl; */ cout << t2 << " is at " << distance(moga.begin(), moga_ite) << endl; // ★追記 } }
一般的な書き方は分かりません。 (追記: 2012/01/26)std::distanceを使うそうです。りょーさんにコメントで教えていただきました。
おまけ: 誤植?
ドリル6の関数宣言が、こうなっています。
template<class Iter1, class Iter2> Iter2 copy(Iter f1, Iter1 e1, Iter2 f2);
第1引数の型は、Iter1が正しいのではないでしょうか。(原書のエラッタにはないですが)
- (ごちゃごちゃだけど、ドリル全体)https://gist.github.com/487eb4f53eb8bb1b3e43
20章は、そんな感じでした。