汎用関数の定義の基本形
template <class typeName> returnType funcName(parameter list) { // 関数本体 }
汎用関数の呼び出しの基本形
funcName<typeName>(parameters);
絶対値を求める関数の汎用化
#include <iostream> /** * int型の入力の絶対値を返す * @param v 入力 * @return |v| */ int absolute_int(int v) { return (v >= 0) ? v : -v; } /** * double型の入力の絶対値を返す * @param v 入力 * @return |v| */ double absolute_double(double v) { return (v >= 0) ? v : -v; } /** * 任意の型の入力の絶対値を返す * @param v 入力 * @return |v| */ template<class T> T absolute(T v) { return (v >= 0) ? v : -v; } int main(int argc, char **argv) { int v_i = -9; double v_d = -9.5; std::cout << absolute_int(v_i) << std::endl; // 9を出力 std::cout << absolute_double(v_d) << std::endl; // 9.5を出力 std::cout << absolute<int>(v_i) << std::endl; // 9を出力 std::cout << absolute<double>(v_d) << std::endl; // 9.5を出力 std::cout << absolute(v_i) << std::endl; // 9を出力 (型名省略可能) std::cout << absolute(v_d) << std::endl; // 9.5を出力 (型名省略可能) return 0; }テンプレートを使わない場合,同じ処理を行うにも関わらず,absolute_intやabsolute_doubleのようにデータ型毎に異なる関数を用意しなければなりません. このような場合,タイプミスにより予期せぬ誤りを引き起こしたり,仕様の変更に対して労力が必要であり,変更ミスのリスクが伴います. テンプレートを用いることで,これらを1つのabsoluteという関数にまとめることができ,このようなリスクをある程度回避できます.
汎用関数の呼び出しの省略形
funcName(parameters);引数などから型が推測可能な場合はtype_name(型名)を省略することができ,通常の関数と同じように記述できます. この場合,コンパイラが型を推測し,自動的に適切な型の関数を生成してくれます.
汎用クラスの定義の基本形
template <class typeName> class ClassName { // クラスの中身 };
汎用クラスの呼び出しの基本形
ClassName<typeName> object;
スタッククラス
#include <iostream> #include <cstdlib> template<class X> class Stack { private: X *data; int pos; int max; public: /** * @brief コンストラクタ * @param size スタックサイズ */ Stack(int size = 10) { data = new X[size]; max = size; pos = 0; } /** * @brief デストラクタ */ ~Stack() { delete data; } /** * @brief スタックの先頭にオブジェクトを入れる * @param v スタックに入れるオブジェクト */ void push(X v) { if (pos >= max) { std::cerr << "stack full\n"; exit(1); } data[pos++] = v; } /** * @breif スタックの先頭のオブジェクトを削除し,そのオブジェクトを返す * @return スタックの先頭のオブジェクト */ X pop() { if (pos <= 0) { std::cerr << "stack empty\n"; exit(1); } return data[--pos]; } /** * @brief スタックが空かどうかを判断 * @retval true スタックが空 * @retval false スタックが空でない */ bool empty() const { return pos == 0; } /** * @brief スタックがいっぱいかどうかを判断 * @retval true スタックがいっぱい * @retval false スタックがいっぱいでない */ bool full() const { return pos == max; } /** * @brief スタックの先頭にあるオブジェクトを取り出す.この時,オブジェクトはスタックから削除されない * @return スタックの先頭のオブジェクト */ X peek() const { // ここを実装せよ } /** * @brief このスタックにあるオブジェクトの位置を1から始まるインデックスで返す.オブジェクトがスタック内にある場合,スタックの先頭からもっとも近いオブジェクトの位置までの距離を返す.スタックの1番上は距離1とする. * @param v 目的のオブジェクト * @return オブジェクトの位置を表す1から始まるスタックの先頭からのインデックス.オブジェクトが見つからない場合は-1 */ int search(X v) { // ここを実装せよ } }; int main(int argc, char** argv) { Stack<int> i_stack; Stack<double> d_stack; i_stack.push(3); i_stack.push(1); i_stack.push(4); d_stack.push(2.5); d_stack.push(7.2); std::cout << "i_stack" << std::endl; while (!i_stack.empty()) { std::cout << i_stack.pop() << std::endl; } std::cout << "d_stack" << std::endl; while (!d_stack.empty()) { std::cout << d_stack.pop() << std::endl; } i_stack.push(3); i_stack.push(1); i_stack.push(4); std::cout << "top of stack = " << i_stack.peek() << std::endl; std::cout << "distance from top of stack = " << i_stack.search(1) << std::endl; std::cout << "distance from top of stack = " << i_stack.search(4) << std::endl; std::cout << "distance from top of stack = " << i_stack.search(3) << std::endl; std::cout << "distance from top of stack = " << i_stack.search(2) << std::endl; return 0; }
出力例
i_stack 4 1 3 d_stack 7.2 2.5 top of stack = 4 distance from top of stack = 2 distance from top of stack = 1 distance from top of stack = 3 distance from top of stack = -1
vectorの使用例
#include <iostream> #include <vector> // vectorを使用する場合には必ずincludeする必要がある /** * @brief ベクトル表示関数 * @param vec 表示するベクトル */ template <class X> void print_vector(std::vector<X> vec) { std::cout << "size=" << vec.size() << std::endl; // サイズを表示 for (int i = 0; i < vec.size(); i++) { std::cout << "v[" << i << "]=" << vec[i] << std::endl; } std::cout << std::endl; } /** * @brief int型のvectorのテスト関数 */ void test_vector_int() { std::vector<int> vi(3); // int型ベクトル (サイズ3) for (int i = 0; i < vi.size(); i++) { // sizeで要素数にアクセスできる vi[i] = i + 1; // 配列のようにアクセスできる } // ベクトルの内容表示 std::cout << "=== phase 1 (int) ===" << std::endl; print_vector(vi); // viの末尾に追加 (サイズが増加) vi.push_back(125); // ベクトルの内容表示 std::cout << "=== phase 2 (int) ===" << std::endl; print_vector(vi); // ベクトルのサイズを 2増やす vi.resize(vi.size() + 2); for (int i = 0; i < vi.size(); i++) { vi[i] = (i + 1) * 3; } // ベクトルの内容表示 std::cout << "=== phase 3 (int) ===" << std::endl; print_vector(vi); } /** * @brief double型のvectorのテスト関数 */ void test_vector_double() { std::vector<double> vd(3); // double型ベクトル (サイズ3) for (int i = 0; i < 3; i++) { vd[i] = (i + 1) * 0.1; } // ベクトルの内容表示 std::cout << "=== phase 1 (double) ===" << std::endl; print_vector(vd); // vdの末尾に追加 (サイズが増加) vd.push_back(1.25); // ベクトルの内容表示 std::cout << "=== phase 2 (double) ===" << std::endl; print_vector(vd); // ベクトルのサイズを 2増やす vd.resize(vd.size() + 2); for (int i = 0; i < vd.size(); i++) { vd[i] = (i + 1)*0.3; } // ベクトルの内容表示 std::cout << "=== phase 3 (double) ===" << std::endl; print_vector(vd); } int main(int argc, char **argv) { test_vector_int(); test_vector_double(); return 0; }
出力例
=== 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
listの使用例
#include <iostream> #include <list> // listを利用するためには必ずincludeする必要がある const int MaxNameLen = 20; /** * @class Student * @brief 生徒情報クラス */ class Student { private: /** 生徒名 */ char name[MaxNameLen]; /** 点数 */ int point; public: /** * @brief 生徒名の設定 * @param n 生徒名 */ void setName(const char *n) { std::strncpy(name, n, MaxNameLen); } /** * @brief 生徒名の取得 * @return 生徒名 */ char *getName() { return name; } /** * @brief 点数の設定 * @param po 点数 */ void setPoint(int po) { point = po; } /** * @brief 点数の取得 * @return 点数 */ int getPoint() const { return point; } /** * @brief 氏名と点数を表示 */ void print() { std::cout << "name=" << name << " point=" << point << std::endl; } }; /** * 登録生徒の情報と平均点を表示 * @param stlst 登録生徒リスト */ void print_list(std::list<Student> stlst) { int total_sum = 0; std::cout << "size=" << stlst.size() << std::endl; for (std::list<Student>::iterator i = stlst.begin(); i != stlst.end(); i++) { // iteratorを使ってlistにアクセス i->print(); total_sum += i->getPoint(); } std::cout << "average=" << ((double) total_sum / stlst.size()) << std::endl; } int main(int argc, char** argv) { std::list<Student> stlst; // Studentクラスのリスト (サイズ 0) Student st; // リストに追加 (サイズが増加) st.setName("Hamm"); st.setPoint(7); stlst.push_back(st); st.setName("Slink"); st.setPoint(4); stlst.push_back(st); st.setName("Potato"); st.setPoint(6); stlst.push_back(st); // リストの内容表示 std::cout << "=== phase 1 ===\n"; print_list(stlst); // リストに追加 (サイズが増加) st.setName("Woody"); st.setPoint(8); stlst.push_back(st); st.setName("Sarge"); st.setPoint(9); stlst.push_back(st); // リストの内容表示 std::cout << "=== phase 2 ===\n"; print_list(stlst); return 0; }
出力例
=== 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
mapの使用例
#include <iostream> #include <map> // mapを利用するためには必ずincludeする必要がある #include <string> // stringを利用する int main(int argc, char** argv) { std::map<std::string, int> symtab; // 文字列からintへのマップ std::map<std::string, int>::iterator i; // イテレータ // マップに登録 symtab.insert(std::pair<std::string, int>("red", 1)); // "red"=>1を登録 symtab.insert(std::pair<std::string, int>("green", 2)); // "green"=>2を登録 symtab.insert(std::make_pair("blue", 4)); // "blue"=>4を登録 pair<>と同じ // マップの中身を表示 std::cout << "=== map ===" << std::endl; for (i = symtab.begin(); i != symtab.end(); i++) { // iteratorを使ってlistにアクセス std::cout << i->first << "=" << i->second << std::endl; } // マップを使って検索 std::cout << "=== search ===" << std::endl; i = symtab.find("red"); // 指定したキーを指す反復子を返す std::cout << i->first << "=" << i->second << std::endl; i = symtab.find("blue"); std::cout << i->first << "=" << i->second << std::endl; return 0; }
出力例
=== map === blue=4 green=2 red=1 === search === red=1 blue=4
algorithmの使用例
#include <iostream> #include <vector> // vectorを利用する #include <algorithm> // algorithmを利用するためには必ずincludeする必要がある int main(int argc, char** argv) { std::vector<int> vi; // int型ベクトル (サイズ0) std::vector<int>::iterator iter; // イテレータ for (int i = 0; i < 10; i++) { vi.push_back((i * 7 + 3) % 11); // 要素を1個追加 } // ソート前のベクトルの内容表示 std::cout << "=== before sort ===" << std::endl; for (iter = vi.begin(); iter != vi.end(); iter++) { std::cout << *iter << std::endl; } std::cout << std::endl; // ベクトルの内容をソート(処理範囲を反復子によって指定) std::sort(vi.begin(), vi.end()); // ソート後のベクトルの内容表示 std::cout << "=== after sort ===" << std::endl; for (iter = vi.begin(); iter != vi.end(); iter++) { std::cout << *iter << std::endl; } std::cout << std::endl; // ベクトルの内容を反転(処理範囲を反復子によって指定) std::reverse(vi.begin(), vi.end()); // 反転後のベクトルの内容表示 std::cout << "=== after reverse ===" << std::endl; for (iter = vi.begin(); iter != vi.end(); iter++) { std::cout << *iter << std::endl; } std::cout << std::endl; return 0; }
出力例
=== 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つずつ参照する」という操作を汎用化したものが,イテレータです. ここで「先頭から最後尾まで要素を1つずつ参照する」という操作を細かく分解してみると以下の4つの操作が出来ればよいことがわかります.
イテレータを用いてコンテナに順にアクセスする例
for (iter = v.begin(); iter != v.end(); iter++) { process(*iter); }このforループにおいて,最初のiter = v.begin()により,先頭位置の設定を行なっています. 次のiter != v.end()により,終了位置に到達したかどうかを判定しています. 3つ目のiter++により,次の要素への移動を行なっています. そして,最後にループボディにある*iterにより,要素の参照を行なっています. 4つの操作は各コンテナクラスによって実装は異なる(つまりvectorはvector,listはlistに応じた実装が必要)が,使う側の立場からすれば,中がどういう実装になっているかは気にする必要はありません.
iteratorの使用例
#include <iostream> #include <vector> #include <list> void test_vector() { std::vector<int> vi; // int型ベクトル (サイズ0) std::vector<int>::iterator iter; // イテレータ std::cout << "[vector]" << std::endl; for (int i = 0; i < 5; i++) { vi.push_back(i + 1); // 要素を1個追加 } // ベクトルの内容表示 std::cout << "=== phase 1 ===" << std::endl; for (iter = vi.begin(); iter != vi.end(); iter++) { std::cout << *iter << std::endl; } std::cout << std::endl; // ベクトルの内容変更 for (iter = vi.begin(); iter != vi.end(); iter++) *iter += 1; // 要素の値を1増加 // ベクトルの内容表示 std::cout << "=== phase 2 ===" << std::endl; for (iter = vi.begin(); iter != vi.end(); iter++) { std::cout << *iter << std::endl; } std::cout << std::endl; } void test_list() { std::list<int> li; // int型リスト (サイズ0) std::list<int>::iterator iter; // イテレータ std::cout << "[list]" << std::endl; for (int i = 0; i < 5; i++) { li.push_back(i + 1); // 要素を1個追加 } // リストの内容表示 std::cout << "=== phase 1 ===" << std::endl; for (iter = li.begin(); iter != li.end(); iter++) { std::cout << *iter << std::endl; } std::cout << std::endl; // リストの内容変更 for (iter = li.begin(); iter != li.end(); iter++) { *iter *= 2; // 要素の値を2倍 } // リストの内容表示 std::cout << "=== phase 2 ===" << std::endl; for (iter = li.begin(); iter != li.end(); iter++) { std::cout << *iter << std::endl; } std::cout << std::endl; } int main(int argc, char **argv) { test_vector(); // ベクトル版 test_list(); // リスト版 return 0; }
出力例
[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
stringの使用例
string s("cat"); // 初期値付き文字列 string s2; // 空の文字列 s2 = "dog"; // 文字列の代入 s = s2; // 文字列のコピー s2 = s1 + " and cat"; // 文字列の連結 s2 += " and mouse"; // 文字列の連結(追加) if (s == s2) { // 文字列の比較 cout << "equal" << endl; }
stringの使用例
#include <iostream> #include <string> // stringを利用するためには必ずincludeする必要がある /** * @brief 2つの文字列を比較し,比較結果を表示 * @param s 文字列1 * @param s2 文字列2 */ void compare_string(std::string s, std::string s2) { std::cout << std::endl; std::cout << "compare_string:" << std::endl; std::cout << "s=" << s << " s2=" << s2 << std::endl; if (s == s2) { // 比較演算子 (文字列の比較) std::cout << "two strings are equal" << std::endl; } else { std::cout << "two strings are not equal" << std::endl; } } int main(int argc, char** argv) { std::string s("one"); // "one"という内容を持つ文字列 std::string s2; // 空の文字列 std::cout << "s=" << s << std::endl; s2 = s; // s2に sを代入 s2 += " two"; // s2の後ろに " two"を連結 s = "zero " + s; // sの前に "zero "を連結 std::cout << "s=" << s << std::endl; std::cout << "s2=" << s2 << std::endl; std::string s3, s4; s3 = "foo"; s4 = "foo"; compare_string(s3, s4); s4 = "bar"; compare_string(s3, s4); return 0; }
出力例
s=one s=zero one s2=one two compare_string: s=foo s2=foo two strings are equal compare_string: s=foo s2=bar two strings are not equal