STLの実現方法は非常に高度で複雑ではあるが、その目指すところは単純で、
ソフトウェアをコンテナ、アルゴリズム、イテレータ(反復子)といった構成要
素に分解し、それらを自由に組み合わせられる汎用部品にしようというもので
ある。
ここではSTLの中心的な存在である以下の3つの要素について説明する。
※ 他にも関数オブジェクトやアダプタなど、STLを真に使いこなす上で重要な
要素があるが、全てを説明するには時間が足りないためここでは説明しない。
興味のある者は、STLに関する書籍を調べてみるとよい。
STLにはいろいろなコンテナクラスが用意されているが、
ここではその中からよく利用されるベクトル、リスト、マップについて説明する。
具体的な使用例は以下の通りである。
1 : //
2 : // vector test
3 : //
4 : #include <iostream>
5 : #include <vector> // vectorを使用する
6 : using namespace std;
7 :
8 : // テンプレートを使って表示関数を定義
9 : template <class X>
10 : void print_vector(vector<X> vec)
11 : {
12 : cout << "size=" << vec.size() << "\n"; // サイズを表示
13 : for (int i = 0; i < vec.size(); i++)
14 : cout << "v[" << i << "]=" << vec[i] << "\n";
15 : cout << endl;
16 : }
17 :
18 : void test_vector_int()
19 : {
20 : vector<int> vi(3); // int型ベクトル (サイズ3)
21 :
22 : for (int i = 0; i < vi.size(); i++)
23 : vi[i] = i+1; // 配列のようにアクセスできる
24 :
25 : // ベクトルの内容表示
26 : cout << "=== phase 1 (int) ===" << endl;
27 : print_vector(vi);
28 :
29 : // viの末尾に追加 (サイズが増加)
30 : vi.push_back(125);
31 :
32 : // ベクトルの内容表示
33 : cout << "=== phase 2 (int) ===" << endl;
34 : print_vector(vi);
35 :
36 : // ベクトルのサイズを 2増やす
37 : vi.resize(vi.size() + 2);
38 : for (int i = 0; i < vi.size(); i++) // VC++ではエラーになるので int を削除すること
39 : vi[i] = (i+1)*3;
40 :
41 : // ベクトルの内容表示
42 : cout << "=== phase 3 (int) ===" << endl;
43 : print_vector(vi);
44 : }
45 :
46 : void test_vector_double()
47 : {
48 : vector<double> vd(3); // double型ベクトル (サイズ3)
49 :
50 : for (int i = 0; i < 3; i++)
51 : vd[i] = (i+1) * 0.1;
52 :
53 : // ベクトルの内容表示
54 : cout << "=== phase 1 (double) ===" << endl;
55 : print_vector(vd);
56 :
57 : // vdの末尾に追加 (サイズが増加)
58 : vd.push_back(1.25);
59 :
60 : // ベクトルの内容表示
61 : cout << "=== phase 2 (double) ===" << endl;
62 : print_vector(vd);
63 :
64 : // ベクトルのサイズを 2増やす
65 : vd.resize(vd.size() + 2);
66 : for (int i = 0; i < vd.size(); i++) // VC++ではエラーになるので int を削除すること
67 : vd[i] = (i+1)*0.3;
68 :
69 : // ベクトルの内容表示
70 : cout << "=== phase 3 (double) ===" << endl;
71 : print_vector(vd);
72 : }
73 :
74 : int main()
75 : {
76 : test_vector_int();
77 : test_vector_double();
78 :
79 : return 0;
80 : }
※ VC++6では、上記コードの一部でエラーが発生していた。
対処法はコード中のコメントに書いている通りである。
これに関しては、「補足:for文の中での宣言」を参照。
vectorを使用する場合には、vectorをインクルードする必要がある。
(上記サンプルコードでは、5行目で vectorをインクルードしている)
18行目から44行目までがint型を格納する vectorの使用例、
46行目から72行目までがdouble型を格納する vectorの使用例である。
各データ型の vectorの作成方法は
汎用クラスのインスタンス化の場合と同様にすればよい。
例えば、int型の vectorを作成する場合は "vector<int>"
double型の vectorを作成する場合は "vector<double>"
と記述する。
また、このサンプルコードでは、vectorに格納されているデータを表示する
関数をテンプレートを用いることで、データ型に依存しない形で定義している。
(9行目から16行目の関数 print_vector() )
上記サンプルコードの実行結果は以下の通りになる。
前半がint型のvector、後半がdouble型のvectorについての表示結果である。
=== phase 1 (int) === size=3 v[0]=1 v[1]=2 v[2]=3 === phase 2 (int) === size=4 v[0]=1 v[1]=2 v[2]=3 v[3]=125 === phase 3 (int) === size=6 v[0]=3 v[1]=6 v[2]=9 v[3]=12 v[4]=15 v[5]=18 === phase 1 (double) === size=3 v[0]=0.1 v[1]=0.2 v[2]=0.3 === phase 2 (double) === size=4 v[0]=0.1 v[1]=0.2 v[2]=0.3 v[3]=1.25 === phase 3 (double) === size=6 v[0]=0.3 v[1]=0.6 v[2]=0.9 v[3]=1.2 v[4]=1.5 v[5]=1.8
具体的な使用例は以下の通りである。
1 : //
2 : // list test
3 : //
4 : #include <iostream>
5 : #include <list> // listを使用する
6 : using namespace std;
7 :
8 : const int MaxNameLen = 20;
9 :
10 : class Student {
11 : char name[MaxNameLen];
12 : int point;
13 : public:
14 : void setName(const char *n) { strncpy(name, n, MaxNameLen); }
15 : char *getName() { return name; }
16 :
17 : void setPoint(int po) { point = po; }
18 : int getPoint() { return point; }
19 :
20 : void print() {
21 : cout << "name=" << name << " point=" << point << endl;
22 : }
23 : };
24 :
25 : void print_list(list<Student> stlst)
26 : {
27 : int total_sum = 0;
28 :
29 : cout << "size=" << stlst.size() << endl;
30 : for (list<Student>::iterator i = stlst.begin(); i != stlst.end(); i++) {
31 : i->print();
32 : total_sum += i->getPoint();
33 : }
34 : cout << "average=" << ((double)total_sum / stlst.size()) << endl;
35 : }
36 :
37 : int main()
38 : {
39 : list<Student> stlst; // Studentクラスのリスト (サイズ 0)
40 : int total_sum;
41 : Student st;
42 :
43 : // リストに追加 (サイズが増加)
44 : st.setName("Hamm"); st.setPoint(7);
45 : stlst.push_back(st);
46 : st.setName("Slink"); st.setPoint(4);
47 : stlst.push_back(st);
48 : st.setName("Potato"); st.setPoint(6);
49 : stlst.push_back(st);
50 :
51 : // リストの内容表示
52 : cout << "=== phase 1 ===\n";
53 : print_list(stlst);
54 :
55 : // リストに追加 (サイズが増加)
56 : st.setName("Woody"); st.setPoint(8);
57 : stlst.push_back(st);
58 : st.setName("Sarge"); st.setPoint(9);
59 : stlst.push_back(st);
60 :
61 : // リストの内容表示
62 : cout << "=== phase 2 ===\n";
63 : print_list(stlst);
64 :
65 : return 0;
66 : }
listを使用する場合は、listをインクルードする必要がある。
このサンプルコードでは、Studentクラスを定義し(10行目から23行目)、
Studentのリストを作成している(39行目)。
また、リストに格納されているデータを表示する関数 print_list()
(25行目から35行目)では、後述するイテレータ(反復子)を用いている。
上記サンプルコードの実行結果は以下の通りになる。
=== phase 1 === size=3 name=Hamm point=7 name=Slink point=4 name=Potato point=6 average=5.66667 === phase 2 === size=5 name=Hamm point=7 name=Slink point=4 name=Potato point=6 name=Woody point=8 name=Sarge point=9 average=6.8
具体的な使用例は以下の通りである。
1 : //
2 : // map test
3 : //
4 : #include <iostream>
5 : #include <map> // mapを使用する
6 : #include <string> // stringクラスを使用する
7 : using namespace std;
8 :
9 : int main()
10 : {
11 : map<string, int> symtab; // 文字列からintへのマップ
12 : map<string, int>::iterator i; // イテレータ
13 :
14 : // マップに登録
15 : symtab.insert(pair<string, int>("red", 1));
16 : symtab.insert(pair<string, int>("green", 2));
17 : symtab.insert(pair<string, int>("blue", 4));
18 :
19 : // マップの中身を表示
20 : cout << "=== map ===\n";
21 : for (i = symtab.begin(); i != symtab.end(); i++) {
22 : cout << i->first << "=" << i->second << endl;
23 : }
24 :
25 : // マップを使って検索
26 : cout << "=== search ===\n";
27 : i = symtab.find("red");
28 : cout << i->first << "=" << i->second << endl;
29 : i = symtab.find("blue");
30 : cout << i->first << "=" << i->second << endl;
31 :
32 : return 0;
33 : }
mapを使用する場合は、mapをインクルードする必要がある。
(上記サンプルコードでは、5行目で mapをインクルードしている)
このプログラムでは文字列と整数値を対応付けるマップを作成している。
ここで、文字列を後述する stringクラスを用いて実現している。
(そのために必要なヘッダ string を6行目でインクルードしている)
実行結果は以下の通りになる。
=== map === blue=4 green=2 red=1 === search === red=1 blue=4
STLのアルゴリズムを使用する場合は、algorithmをインクルードする
必要がある。
具体的な使用例は以下の通りである。
1 : //
2 : // algorithm test
3 : //
4 : #include <iostream>
5 : #include <vector> // vectorを使用する
6 : #include <algorithm> // algorithmを使用する
7 : using namespace std;
8 :
9 : int main()
10 : {
11 : vector<int> vi; // int型ベクトル (サイズ0)
12 : vector<int>::iterator iter; // イテレータ
13 :
14 : for (int i = 0; i < 10; i++)
15 : vi.push_back((i*7+3)%11); // 要素を1個追加
16 :
17 : // ソート前のベクトルの内容表示
18 : cout << "=== before sort ===" << endl;
19 : for (iter = vi.begin(); iter != vi.end(); iter++)
20 : cout << *iter << "\n";
21 : cout << endl;
22 :
23 : // ベクトルの内容をソート
24 : sort(vi.begin(), vi.end());
25 :
26 : // ソート後のベクトルの内容表示
27 : cout << "=== after sort ===" << endl;
28 : for (iter = vi.begin(); iter != vi.end(); iter++)
29 : cout << *iter << "\n";
30 : cout << endl;
31 :
32 : // ベクトルの内容を反転
33 : reverse(vi.begin(), vi.end());
34 :
35 : // 反転後のベクトルの内容表示
36 : cout << "=== after reverse ===" << endl;
37 : for (iter = vi.begin(); iter != vi.end(); iter++)
38 : cout << *iter << "\n";
39 : cout << endl;
40 :
41 : return 0;
42 : }
ここでは、ソートを行なうアルゴリズム sort()と、
内容の逆順にする reverse()を使った例を示す。
sort()と reverse()のテンプレート指定はそれぞれ以下の通りであり、
処理対象の範囲を後述するイテレータ (反復子)により与えるようになっ
ている。
template <class RandIter> void sort(RandIter start, RandIter end);
template <class BiIter> void reverse(BiIter start, BiIter end);
実行結果は以下の通りになる。
=== before sort === 3 10 6 2 9 5 1 8 4 0 === after sort === 0 1 2 3 4 5 6 8 9 10 === after reverse === 10 9 8 6 5 4 3 2 1 0
/* 配列を端から順に参照 */
for (i = 0; i < N; i++)
process(a[i]);
/* リストを端から順に参照 */
for (p = base; p != NULL; p = p->next)
process(p->data);
しかし、本来これらは「先頭から最後尾まで要素を1つずつ参照する」という
意味では同じものである。
ここで「先頭から最後尾まで要素を1つずつ参照する」という操作を細かく
分解してみると以下の4つの操作が出来ればよいことが分かる。
イテレータでは上記の4操作を以下のようにして実現する。
以下のようにしてこの4つの操作を用いることで、要素を順次参照するという
操作が実現できる。
※ vは vectorや listなどのコンテナクラスのオブジェクト
iterはコンテナクラスに対応したイテレータ
for (iter = v.begin(); iter != v.end(); iter++)
process(*iter);
このforループにおいて、最初の iter = v.begin()により、
先頭位置の設定を行なっている。
4つの操作は各コンテナクラスによって実装は異なる(つまりvectorは vector、
listは listに応じた実装が必要)が、使う側の立場からすれば、
中がどういう実装になっているかは気にする必要はない。
具体的な使用例は以下の通りである。
1 : //
2 : // iterator test
3 : //
4 : #include <iostream>
5 : #include <vector> // vectorを使用する
6 : #include <list> // listを使用する
7 : using namespace std;
8 :
9 : void test_vector()
10 : {
11 : vector<int> vi; // int型ベクトル (サイズ0)
12 : vector<int>::iterator iter; // イテレータ
13 :
14 : cout << "[vector]" << endl;
15 :
16 : for (int i = 0; i < 5; i++)
17 : vi.push_back(i+1); // 要素を1個追加
18 :
19 : // ベクトルの内容表示
20 : cout << "=== phase 1 ===" << endl;
21 : for (iter = vi.begin(); iter != vi.end(); iter++)
22 : cout << *iter << "\n";
23 : cout << endl;
24 :
25 : // ベクトルの内容変更
26 : for (iter = vi.begin(); iter != vi.end(); iter++)
27 : *iter += 1; // 要素の値を1増加
28 :
29 : // ベクトルの内容表示
30 : cout << "=== phase 2 ===" << endl;
31 : for (iter = vi.begin(); iter != vi.end(); iter++)
32 : cout << *iter << "\n";
33 : cout << endl;
34 : }
35 :
36 : void test_list()
37 : {
38 : list<int> li; // int型リスト (サイズ0)
39 : list<int>::iterator iter; // イテレータ
40 :
41 : cout << "[list]" << endl;
42 :
43 : for (int i = 0; i < 5; i++)
44 : li.push_back(i+1); // 要素を1個追加
45 :
46 : // リストの内容表示
47 : cout << "=== phase 1 ===" << endl;
48 : for (iter = li.begin(); iter != li.end(); iter++)
49 : cout << *iter << "\n";
50 : cout << endl;
51 :
52 : // リストの内容変更
53 : for (iter = li.begin(); iter != li.end(); iter++)
54 : *iter *= 2; // 要素の値を2倍
55 :
56 : // リストの内容表示
57 : cout << "=== phase 2 ===" << endl;
58 : for (iter = li.begin(); iter != li.end(); iter++)
59 : cout << *iter << "\n";
60 : cout << endl;
61 : }
62 :
63 : int main()
64 : {
65 : test_vector(); // ベクトル版
66 : test_list(); // リスト版
67 :
68 : return 0;
69 : }
9行目から34行目の関数 test_vector() がvectorクラスでイテレータを
使った例、
36行目から61行目の関数 test_list() がlistクラスでイテレータを使っ
た例である。
実行結果は以下の通りになる。
[vector] === phase 1 === 1 2 3 4 5 === phase 2 === 2 3 4 5 6 [list] === phase 1 === 1 2 3 4 5 === phase 2 === 2 4 6 8 10
前半が vectorに対してイテレータを用いて操作をした場合の表示結果で、
後半が listに対してイテレータを用いて操作をした場合の表示結果である。