今回は標準ライブラリ (STL) の中から「文字列 (string)」を取り上げます。STL には basic_string というテンプレートがあり、string は basic_string<char> の別名として定義されています。string は vector とよく似ていますが、文字列を操作するのに都合がよいメンバ関数が追加されているところが異なります。C++の場合、Cスタイル文字列よりも string を使ったほうが便利です。本ページでは string の基本的な使い方を簡単に説明します。
string はCスタイル文字列や他の string、それらの部分文字列を使って初期化することができます。文字や整数で string を初期化することはできませんが、文字と長さを指定して string を初期化することは可能です。
簡単な例を示しましょう。
リスト : string の宣言と初期化 (sample2901.cpp) #include <iostream> #include <vector> using namespace std; void print_string(string& s) { cout << "[" << s << "]\n"; } int main() { string s0; // 空文字列 print_string(s0); string s1("hello, world"); // リテラル print_string(s1); const char* a = "Hello, World"; // ポインタ string s2(a); print_string(s2); string s3(s1); // s1 をコピー print_string(s3); string s4(8, 'a'); print_string(s4); // 文字と長さの指定 const char* b = "foo bar baz oops"; string s5(b + 4, 3); // b[4] から 3 文字 print_string(s5); vector<char> c = {'a', 'b', 'c', 'd', 'e', 'f'}; string s6(c.begin(), c.end()); print_string(s6); }
$ clang++ sample2901.cpp $ ./a.out [] [hello, world] [Hello, World] [hello, world] [aaaaaaaa] [bar] [abcdef]
文字を格納しているコンテナクラスであれば、イテレータを指定して string を生成することもできます。
string は vector と同様に添字演算子 [] やメンバ関数 at で要素 (文字) にアクセスすることができます。メンバ関数には front や back も用意されています。
簡単な例を示します。
リスト : 要素のアクセス (sample2902.cpp) #include <iostream> using namespace std; int main() { string s = "hello, world"; cout << s[0] << endl; cout << s[s.length() - 1] << endl; s[0] = 'H'; s[s.length() - 1] = 'D'; for (auto x : s) cout << x; cout << endl; }
$ clang++ sample2902.cpp $ ./a.out h d Hello, worlD
length は文字列の長さを求めるメンバ関数です。コンテナクラスのメンバ関数 size と同じです。string は範囲 for 文で要素にアクセスすることもできます。
string はランダムイテレータをサポートしています。どの要素でも定数時間 O(1) でアクセスすることが可能です。イテレータを生成するメンバ関数を下表に示します。
メンバ関数 | 機能 |
---|---|
begin() | 先頭要素を指し示すイテレータを返す |
end() | 終端を指し示すイテレータを返す |
cbegin() | 先頭要素を指し示す const イテレータを返す |
cend() | 終端を指し示す const イテレータを返す |
rbegin() | 先頭要素を指し示すリバースイテレータを返す |
rend() | 終端を指し示すリバースイテレータを返す |
crbegin() | 先頭要素を指し示す const リバースイテレータを返す |
crend() | 終端を指し示す const リバースイテレータを返す |
const イテレータは要素を更新することができません。リバースイテレータは末尾要素が先頭で、先頭要素が末尾になります。要素が n 個ある場合、n - 1 番目の要素が先頭で、0 番目の要素が末尾になります。
イテレータのデータ型は次のようになります。
string::iterator // 通常のイテレータ string::const_iterator // const イテレータ string::reverse_iterator // リバースイテレータ string::const_reverse_iterator // const リバースイテレータ
最近の規格 (C++11) を利用できるコンパイラでは auto を使ったほうが簡単でしょう。
簡単な使用例を示します。
リスト : イテレータの使用例 (sample2903.cpp) #include <iostream> #include <algorithm> using namespace std; int main() { string s = "hello, world"; for (string::const_iterator iter = s.cbegin(); iter != s.cend(); ++iter) cout << *iter; cout << endl; for (auto iter = s.rbegin(); iter != s.rend(); ++iter) cout << *iter; cout << endl; auto p = find(s.begin(), s.end(), 'w'); if (p != s.end()) cout << *p << endl; }
$ clang++ sample2903.cpp $ ./a.out hello, world dlrow ,olleh w
string はイテレータをサポートしているので、STL の algorithm に用意されている関数、たとえば find や for_each などを利用することができます。ただし、文字列の操作は汎用の関数を使うよりも、string のメンバ関数を使ったほうが便利です。
string は演算子 + で連結することができます。s1 + s2 は文字列 s1 と s2 を連結した新しい文字列を生成します。
簡単な使用例を示します。
リスト : 文字列の連結 (sample2904.cpp) #include <iostream> using namespace std; int main() { string s1 = "hello"; string s2 = "world"; string s3 = s1 + ", " + s2; cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; }
$ clang++ sample2904.cpp $ ./a.out hello world hello, world
string は vector と同様にメンバ関数 insert で文字列の途中にデータを挿入することができますが、データを末尾に追加する演算子 += やメンバ関数 append も用意されています。もちろん、push_back もあります。
メンバ関数 | 機能 |
---|---|
push_back(c) | 末尾に文字 c を追加する |
operator+=(str) | 末尾に文字列 str を追加する |
operator+=(c) | 末尾に文字 c を追加する |
append(str) | 末尾に文字列 str を追加する |
append(n, c) | 末尾に文字 c を n 個追加する |
insert(pos, str) | 位置 pos に文字列 str を挿入する |
insert(pos, n, c) | 位置 pos に文字 c を n 個挿入する |
insert(it, c) | イテレータ it の位置に文字 c を挿入する |
insert(it, n, c) | イテレータ it の位置に文字 c を n 個挿入する |
insert(it, first, last) | イテレータ it の位置にイテレータ [first, last) が示す区間の文字を挿入する |
簡単な例を示します。
リスト : 文字列の末尾にデータを追加する (sample2905.cpp) #include <iostream> #include <vector> using namespace std; int main() { string s1; s1.push_back('f'); s1.push_back('o'); s1.push_back('o'); cout << s1 << endl; string s2 = "bar"; s1 += " "; s1 += s2; cout << s1 << endl; s1.append(" baz"); cout << s1 << endl; s1.append(5, '!'); cout << s1 << endl; s1.insert(0, "abc "); cout << s1 << endl; string s3 = "oops "; s1.insert(4, s3); cout << s1 << endl; vector<char> s4 = {'A', 'B', 'C'}; s1.insert(s1.begin(), s4.begin(), s4.end()); cout << s1 << endl; }
$ clang++ sample2905.cpp $ ./a.out foo foo bar foo bar baz foo bar baz!!!!! abc foo bar baz!!!!! abc oops foo bar baz!!!!! ABCabc oops foo bar baz!!!!!
末尾の文字は pop_back で取り除くことができます。途中の文字や文字列はメンバ関数 erase で取り除くことができます。
メンバ関数 | 機能 |
---|---|
pop_back() | 末尾から 1 文字削除する |
erase() | 文字をすべて削除する (空文字列) |
erase(pos, n) | 位置 pos から n 文字を削除する |
erase(it) | イテレータ it の位置の文字を削除する |
erase(first, last) | イテレータ [first, last) の区間にある文字を削除する |
簡単な使用例を示します。
リスト : 文字列の削除 (sample2906.cpp) #include <iostream> using namespace std; int main() { string s1 = "foo bar baz oops!"; cout << s1.back() << endl; s1.pop_back(); cout << s1 << endl; cout << s1.front() << endl; s1.erase(s1.begin()); // イテレータの位置の文字を削除 cout << s1 << endl; s1.erase(0, 3); // 0 番目から 3 文字を削除 cout << s1 << endl; s1.erase(s1.begin()); cout << s1 << endl; s1.erase(s1.begin(), s1.begin() + 3); cout << s1 << endl; }
$ clang++ sample2906.cpp $ ./a.out ! foo bar baz oops f oo bar baz oops bar baz oops ar baz oops baz oops
文字列の探索はメンバ関数 find を使うと簡単です。
size_type find(文字列, 検索開始位置 = 0);
find は引数の文字列と等しい部分文字列を探索し、見つけたらその位置を返します。見つからない場合は string::npos を返します。
簡単な例を示しましょう。
リスト : find の使用例 (sample2907.cpp) #include <iostream> using namespace std; int main() { string s1 = "foo bar baz foo bar baz oops!"; string s2 = "baz"; int n = 0; while (true) { n = s1.find(s2, n); if (n == string::npos) break; cout << n << endl; n += s2.length(); } }
$ clang++ sample2907.cpp $ ./a.out 8 20
このほかにも、末尾から部分文字列を探索するメンバ関数 rfind や、引数の文字列に含まれる文字を探索するメンバ関数 find_first_of や find_last_of、引数の文字列に含まれていない文字を探索するメンバ関数 find_first_not_of や find_last_not_of などがあります。詳細はC++のリファレンスマニュアルをお読みください。
メンバ関数 replace を使うと、文字列を置換することができます。
string& replace(開始インデックス, 文字数, 置換文字列); string& replace(開始イテレータ, 終了イテレータ, 置換文字列);
replace は置換開始位置から文字数の部分文字列を置換文字列に置き換えます。イテレータで置換する部分を指定することもできます。
簡単な使用例を示します。
リスト : replace の使用例 (sample2908.cpp) #include <iostream> using namespace std; int main() { string s1 = "foo bar baz oops!"; s1.replace(4, 3, "ABCD"); cout << s1 << endl; }
$ clang++ sample2908.cpp $ ./a.out foo ABCD baz oops!
replace にはいろいろな使い方があります。詳細はリファレンスマニュアルをお読みください。
メンバ関数 substr を使うと、文字列から部分文字列を求めることができます。
string substr(開始位置, 文字数);
substr は開始位置から文字数で指定した部分文字列を格納した新しい string を生成して返します。
簡単な使用例を示します。
リスト : substr の使用例 (sample2909.cpp) #include <iostream> using namespace std; int main() { string s = "abcdefghijklmn"; for (int i = 0; i <= s.length(); i++) cout << s.substr(i, s.length() - i) << endl; }
$ clang++ sample2909.cpp $ ./a.out abcdefghijklmn bcdefghijklmn cdefghijklmn defghijklmn efghijklmn fghijklmn ghijklmn hijklmn ijklmn jklmn klmn lmn mn n
string はメンバ関数 c_str や data でCスタイル文字列に変換することができます。
const char* c_str(); const char* data();
簡単な使用例を示します。
リスト : c_str と data の使用例 (sample2910.cpp) #include <iostream> #include <cstring> using namespace std; int main() { string s1 = "hello, world"; cout << s1.c_str() << endl; cout << strlen(s1.data()) << endl; }
$ clang++ sample2910.cpp $ ./a.out hello, world 12
今回は C++11 で導入された「タプル (tuple)」というコンテナクラスを簡単に説明します。タプルは SML/NJ, OCaml, Haskell など多くの関数型言語でサポートされている immutable なデータ構造です。スクリプト言語では Python がタプルをサポートしています。
C++の場合、tuple は構造体 pair を拡張したデータ構造です。pair は 2 つの要素しか格納できませんが、tuple は複数の要素を格納できるクラスです。pair と同様に、要素のデータ型は異なっていてもかまいません。pair はメンバ変数 first, second の値を書き換えることができますが、tuple は要素の値を書き換えることはできません。ご注意くださいませ。
tuple を使用するときは、ヘッダファイル tuple をインクルードしてください。変数の宣言は次のように行います。
tuple はテンプレートなので、< > の中に格納する要素のデータ型を指定してください。1 の場合、指定したデータ型のデフォルト値を格納したタプルが生成されます。普通は 2 のように、値を指定してタプルを生成するか、関数 make_tuple を使います。make_tuple はあとで説明します。
このほかにも、コピーコンストラクタやムーブコンストラクタ、pair からタプルを構築するコンストラクタなどが用意されています。詳細はC++のリファレンスマニュアルをお読みください。
要素は関数 get でアクセスすることができます。
get<位置>(タプル);
位置は配列と同じく先頭要素が 0 番目になります。範囲外の位置を指定するとコンパイルエラーになります。
簡単な使用例を示しましょう。
リスト : tuple の簡単な使用例 (sample2911.cpp) #include <iostream> #include <tuple> using namespace std; int main() { tuple<int, double, string> a; cout << get<0>(a) << endl; cout << get<1>(a) << endl; cout << get<2>(a) << endl; tuple<int, double, string> b(1, 1.234, "foo"); cout << get<0>(b) << endl; cout << get<1>(b) << endl; cout << get<2>(b) << endl; pair<string, int> p = {"bar", 20}; tuple<string, int> c(p); cout << get<0>(c) << endl; cout << get<1>(c) << endl; }
実行結果は次のようになります。
$ clang++ sample2911.cpp $ ./a.out 0 0 1 1.234 foo bar 20
変数 a は int, double, string を格納するタプルがセットされます。値はデフォルト値 (0, 0.0, "") になります。変数 b には 1, 1.234, foo を格納したタプルがセットされます。変数 c のタプルは pair<string, int> から生成されます。
タプルは関数 make_tuple で生成することもできます。この場合、make_tuple の引数がタプルの要素にコピーされます。引数が右辺値であれば、ムーブセマンティクスが働きます。make_tuple を使うと、変数の宣言に auto を使うことができるので便利です。
簡単な使用例を示します。
リスト : make_tuple の使用例 (sample2912.cpp) #include <iostream> #include <tuple> using namespace std; class Foo { int x; public: Foo() : x(0) {} Foo(int n) : x(n) {} Foo(const Foo& a) : x(a.x) { cout << "copy " << x << endl; } Foo(Foo&& a) : x(a.x) { cout << "move " << x << endl; } int get_x() const { return x; } }; int main() { auto a = make_tuple(1, 1.234, "foo"); cout << get<0>(a) << endl; cout << get<1>(a) << endl; cout << get<2>(a) << endl; Foo b(10); auto c = make_tuple(b, Foo(20)); cout << get<0>(c).get_x() << endl; cout << get<1>(c).get_x() << endl; }
$ clang++ sample2912.cpp $ ./a.out 1 1.234 foo move 20 copy 10 10 20
変数 a, c の値は make_tuple の返り値なので、データ型は auto で宣言することができます。make_tuple に変数 b の値を渡すとコピーコンストラクタが呼び出され、右辺値 Foo(20) を渡すとムーブコンストラクタが呼び出されることがわかります。
tuple はポインタや参照も格納することもできます。次の例を見てください。
リスト : tuple にポインタや参照を格納する (sample2913.cpp) #include <iostream> #include <tuple> using namespace std; int main() { int a = 1; double b = 1.2345; string c = "hello, world"; tuple<int, double*, string&> d(1, &b, c); cout << get<0>(d) << endl; cout << *(get<1>(d)) << endl; cout << get<2>(d) << endl; *(get<1>(d)) = 12.345; get<2>(d) = "foo bar"; cout << *(get<1>(d)) << endl; cout << get<2>(d) << endl; }
$ clang++ sample2913.cpp $ ./a.out 1 1.2345 hello, world 12.345 foo bar
ポインタや参照の場合、ポインタ先や参照先の変数の値を書き換えることができます。この場合、tuple に格納されている値は書き換わっていないことに注意してください。
tuple に参照を格納する場合は関数 tie を使うと便利です。tie は引数の参照を格納したタプルを生成して返します。簡単な例を示します。
リスト : 関数 tie の使用例 (sample2914.cpp) #include <iostream> #include <tuple> using namespace std; int main() { int a = 1; double b = 1.2345; string c = "hello, world"; auto d = tie(a, b, c); cout << get<0>(d) << endl; cout << get<1>(d) << endl; cout << get<2>(d) << endl; get<0>(d) *= 10; get<1>(d) *= 100; cout << get<0>(d) << endl; cout << get<1>(d) << endl; }
$ clang++ sample2914.cpp $ ./a.out 1 1.2345 hello, world 10 123.45
変数 d のデータ型は tuple<int&, double&, string&> になります。tie は変数 a, b, c の参照を格納したタプルを生成して返します。参照先の変数の値を書き換えることもできます。
もう一つ tie には便利な使い方があって、tuple に格納されている値を変数に取り出すことができます。
auto t = tuple<データ型, ...>(値, ...); tie(変数, ...) = t;
tie にタプル t を代入すると、t の要素を tie で指定した変数にセットすることができます。
簡単な使用例を示します。
リスト : tie の使用例 (sample2915.cpp) #include <iostream> #include <tuple> using namespace std; int main() { int a; double b; string c; auto d = make_tuple(1, 1.2345, "foo"); tie(a, b, c) = d; cout << a << endl; cout << b << endl; cout << c << endl; }
$ clang++ sample2915.cpp $ ./a.out 1 1.2345 foo
一般に、関数の返り値はひとつしかありませんが、なかには複数の値を返すことができるプログラミング言語もあります。たとえば Lisp / Scheme では、この機能を「多値 (Multiple Values)」といい、複数の値を効率的に返すことができるようになっています。C++の場合、クラスや構造体を使って複数の値を返すことができますが、tuple と tie を使うともっと簡単に「多値」を実現することができます。
簡単な例を示しましょう。
リスト : tuple による多値の実装 (sample2916.cpp) #include <iostream> #include <vector> #include <tuple> using namespace std; template<class T> tuple<T, T> max_min(vector<T>& vec) { T x = vec[0]; T y = vec[0]; for (int i = 0; i < vec.size(); i++) { if (x < vec[i]) x = vec[i]; else if (y > vec[i]) y = vec[i]; } return make_tuple(x, y); } int main() { vector<int> a = {5, 6, 4, 7, 3, 8, 2, 9, 1}; int x, y; tie(x, y) = max_min(a); cout << x << endl; cout << y << endl; }
$ clang++ sample2916.cpp $ ./a.out 9 1
関数 max_min は vector の中から最大値と最小値を求めて、それらをタプルに格納して返します。呼び出す側では、最大値と最小値を受け取る変数 x, y を用意して、tie(x, y) で指定すれば、max_min の返り値を変数 x, y にセットすることができます。
受け取りたくない値がある場合は、tie の中で ignore を指定してください。たとえば、最大値だけ受け取るときは次のように指定します。
リスト : ignore の使用例 int x; tie(x, ignore) = max_min(a);
このほかにも tuple には便利な関数が用意されています。詳細はC++のリファレンスマニュアルをお読みください。