vectorとイテレータ
#include <iostream> #include <vector> #include <algorithm> int main() { using std::cout; using std::endl; //普通の配列っぽく宣言 std::vector<int> arr = { 1,2,3 }; for (int i = 0; i < arr.size(); i++) { cout << arr[i] << ","; } cout << endl; //要素の追加 cout << "size:" << arr.size() << endl; for (auto i = 0; i < 10; i++) { arr.push_back(i); } cout << "size:" << arr.size() << endl; //範囲ループ for (auto x : arr) { cout << x << ","; } cout << endl; //要素の挿入 index=2の位置に100を挿入 arr.insert(arr.begin() + 2, 100); //arr.begin() => arr[0] //arr.begin()+2 => arr[0+2] => arr[2] //範囲ループ for (auto x : arr) { cout << x << ","; } cout << endl; //要素の削除 index=2の位置の要素を削除 arr.erase(arr.begin() + 2); for (auto x : arr) { cout << x << ","; } cout << endl; //イテレータ(繰り返しのためのポインタ) // std::vector<int>::iterator theI; //for (std::vector<int>::iterator theI = arr.begin(); cout << "イテレータのサンプル" << endl; for(auto theI = arr.begin(); theI != arr.end(); theI++) { cout << (*theI) << ","; } cout << endl; cout << "逆イテレータのサンプル" << endl; for (auto theI = arr.rbegin(); theI != arr.rend(); theI++) { cout << (*theI) << ","; } cout << endl; cout << "std::sort のサンプル" << endl; std::sort(arr.begin(), arr.end()); for (auto theI = arr.begin(); theI != arr.end(); theI++) { cout << (*theI) << ","; } cout << endl; cout << "std::swap のサンプル" << endl; std::swap(arr[0], arr[2]); for (auto theI = arr.begin(); theI != arr.end(); theI++) { cout << (*theI) << ","; } arr.clear(); cout << "size:" << arr.size() << endl; return 0; }
イテレータとstlコンテナ
STLでデータを入れるための箱を、コンテナと呼ぶのは覚えてる?
vectorや、list、mapはデータを入れるためのコンテナということになる。
その他それらを扱うための関数群や、便利に使うためのクラス群をまとめてSTLと呼んでいる。
コンテナを使うために必須になってくるのが、イテレータである。
イテレータは、繰り返し処理を便利に行うための仕組みで、野生のC++状態で言うとポインタの代わりになるものである。
機能としては、
- コンテナのある要素の位置を示す
- コンテナのある位置の要素を参照する
という、実際ポインタと同じ役割を果たす。
- "main.cpp"
#include <iostream> int main() { int array[10]{1,2,3,4,5,6,7,8,9,10}; int* ptr = array; std::cout << *ptr << std::endl; std::cout << *(ptr + 1) << std::endl; std::cout << *(ptr + 2) << std::endl; }
野生のC++では、配列の要素にポインタ経由でアクセスするのにこのような形でアクセスするのが定番。
これはもう慣れたもんかな?
ポインタは、配列の型によって、+1すると配列の要素1個のメモリ分飛んでくれるので、どんな型、クラス、構造体の配列に対しても+1するとその要素1個分ずれてくれるありがたいお方だった。
- "main.cpp"
#include <iostream> class poo { public: poo(int _a, int _b):a(_a),b(_b){} int a; int b; }; const int arrNum = 5; int main() { poo array[arrNum]{ {1,1},{2,2},{3,3},{4,4},{5,5} }; poo* ptr = array; for(int i=0; i<arrNum; i++){ std::cout << ptr->a << ", " << ptr->b << std::endl; ptr++; } }
この様にポインタをインクリメント、デクリメントして、繰り返しの中で便利に使っていくと、配列へのアクセスがとても便利に行えるんだったね。
しかしこの方法では、配列の初めの要素(=配列名)、配列のサイズ(=arrNum)がわかっていないと、領域オーバーしちゃうことがある。
などなどの様々な問題をはらんでいる。
STLコンテナのイテレータは、ポインタと同じように使えて、なおかつ安全性を確保しつつ、コンテナの特性に合わせた便利なアクセス方法を提供する。
(すてきやん)
- "theMain.cpp"
#include <iostream> #include <array> #include <ostream> class poo { public: poo(int _a, int _b):a(_a),b(_b){} int a; int b; //<<演算子をオーバーロードしてフレンド登録(習ってないから気にするな!) //これをやるとcoutに自分のクラスを渡したときの挙動を書ける(かっこいい) friend std::ostream& operator<<(std::ostream& os, const poo& _dat); friend std::ostream& operator<<(std::ostream& os, const poo* _dat); }; //coutにpooの参照を渡したときの挙動を書く std::ostream& operator<<(std::ostream& os, const poo& _dat) { os << _dat.a << ", " << _dat.b; return os; } //coutにpooのポインタを渡したときの挙動を書く std::ostream& operator<<(std::ostream& os, const poo* _dat) { os << _dat->a << ", " << _dat->b; return os; } const int arrNum = 5; int main() { std::array<poo, arrNum> arr = { {{1,1}, {2,2}, {3,3}, {4,4}, {5,5}} }; std::array<poo, arrNum>::iterator theI;//イテレータを宣言 theI = arr.begin();//begin()は配列の先頭を指すポインタを返す std::cout << theI << std::endl; theI = arr.end();//end()は配列の最後の要素の次にある要素のポインタを返す std::cout << theI << std::endl; theI = arr.end() -1;//end()-1は配列の最後の要素のポインタを返す std::cout << theI << std::endl; }
イテレータの型は、コンテナの型::iteratorとなる。
(コンテナの型=コンテナ+型パラメータだよ。例)array<int, 10>とか vector<double>とか)
上のソースの様にコンテナのインスタンスから、イテレータを取得できる。
- begin()
- コンテナの先頭要素へのポインタを返す
- end()
- コンテナの最終要素の次の要素(そこまで行ったら領域オーバー)のポインタを返す
- cbegin()
- 先頭要素を指す読み取り専用イテレータを取得
- cend()
- 末尾の次の要素を指す読み取り専用イテレータを取得
や、逆イテレータなど、コンテナによって、いろいろある。
あとは、
- ++でポインタをコンテナの次の要素に進める(+1でも一緒)
- –でポインタをコンテナの前の要素に戻す(-1でも一緒)
コンテナの種類によっては使えないイテレータ操作とかがあるよ。
(例えば、単方向リストでは++できるけど、--はできない。単方向リストだからね。)
あと、いちいち、コンテナ+型パラメータ::iteratorとか書いてられないので、autoやauto&を使って、推論してもう事ができる(なれたらこっちを使おう)
- "イテレータの型推論"
std::array<poo, arrNum>::iterator theI;//イテレータを取得 theI = arr.begin();//begin()は配列の先頭を指すポインタを返す //ここまでをauto推論を使って auto itr = arr.begin(); //と書くことができる
なので、上のarrayのfor文表示に関しては以下の様にかける。
- "theMain"
#include <iostream> #include <array> #include <ostream> class poo { public: poo(int _a, int _b):a(_a),b(_b){} int a; int b; //<<演算子をオーバーロードしてフレンド登録(習ってないから気にするな!) //これをやるとcoutに自分のクラスを渡したときの挙動を書ける(かっこいい) friend std::ostream& operator<<(std::ostream& os, const poo& _dat); friend std::ostream& operator<<(std::ostream& os, const poo* _dat); }; //coutにpooの参照を渡したときの挙動を書く std::ostream& operator<<(std::ostream& os, const poo& _dat) { os << _dat.a << ", " << _dat.b; return os; } //coutにpooのポインタを渡したときの挙動を書く std::ostream& operator<<(std::ostream& os, const poo* _dat) { os << _dat->a << ", " << _dat->b; return os; } const int arrNum = 5; int main() { std::array<poo, arrNum> arr = { {{1,1}, {2,2}, {3,3}, {4,4}, {5,5}} }; for(auto itr=arr.begin(); itr != arr.end(); itr++){ std::cout << itr << std::endl; } }
お題
- vectorとか、mapコンテナにpoo型のデータを突っ込んで、同じようにfor文とイテレータで回してみよう!
- mapは2つデータ組(キーとデータ)がある。.firstにキー .secondにデータが納められる
- mapは[]演算子によって map<string, int> mp;みたいな宣言なら mp[キー(string)]でintのデータを取り出せる。
以下ヒント(ほとんど答え)
#include <iostream> #include <array> #include <ostream> #include <map> #include <string> class poo { public: poo(int _a, int _b) :a(_a), b(_b) {} int a; int b; //<<演算子をオーバーロードしてフレンド登録(習ってないから気にするな!) //これをやるとcoutに自分のクラスを渡したときの挙動を書ける(かっこいい) friend std::ostream& operator<<(std::ostream& os, const poo& _dat); friend std::ostream& operator<<(std::ostream& os, const poo* _dat); }; //coutにpooの参照を渡したときの挙動を書く std::ostream& operator<<(std::ostream& os, const poo& _dat) { os << _dat.a << ", " << _dat.b; return os; } //coutにpooのポインタを渡したときの挙動を書く std::ostream& operator<<(std::ostream& os, const poo* _dat) { os << _dat->a << ", " << _dat->b; return os; } int main() { std::map<std::string, poo> arr = { {//"キー", {poo型のデータ} {"a",{1,1}}, {"b",{2,2}}, {"c",{3,3}}, {"d",{4,4}}, {"e",{5,5}} } }; //arr[a]には、poo型で{1,1} //arr[e]には、poo型で{5,5}が入っている //mapの中身を出力:mapはキーとデータを持つ、キーは->firstで、データは->secondでアクセスできる //itrは、map<string,poo>なので、itr->firstとitr->secondでstringとpooのデータにアクセスできる for (auto itr = arr.begin(); itr != arr.end(); itr++) { std::cout << frist(キーにアクセス) << ": " << second(データにアクセス) << std::endl; } }