M.Hiroi's Home Page

Linux Programming

お気楽C++プログラミング超入門

[ PrevPage | C++ | NextPage ]

文字列 (string)

今回は標準ライブラリ (STL) の中から「文字列 (string)」を取り上げます。STL には basic_string というテンプレートがあり、string は basic_string<char> の別名として定義されています。string は vector とよく似ていますが、文字列を操作するのに都合がよいメンバ関数が追加されているところが異なります。C++の場合、Cスタイル文字列よりも string を使ったほうが便利です。本ページでは 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 のイテレータ

string はランダムイテレータをサポートしています。どの要素でも定数時間 O(1) でアクセスすることが可能です。イテレータを生成するメンバ関数を下表に示します。

表 : イテレータの生成 (string)
メンバ関数機能
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

●Cスタイル文字列への変換

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

初版 2015 年 11 月 7 日
改訂 2023 年 4 月 15 日

タプル (tuple)

今回は C++11 で導入された「タプル (tuple)」というコンテナクラスを簡単に説明します。タプルは SML/NJ, OCaml, Haskell など多くの関数型言語でサポートされている immutable なデータ構造です。スクリプト言語では Python がタプルをサポートしています。

●タプルとは?

C++の場合、tuple は構造体 pair を拡張したデータ構造です。pair は 2 つの要素しか格納できませんが、tuple は複数の要素を格納できるクラスです。pair と同様に、要素のデータ型は異なっていてもかまいません。pair はメンバ変数 first, second の値を書き換えることができますが、tuple は要素の値を書き換えることはできません。ご注意くださいませ。

tuple を使用するときは、ヘッダファイル tuple をインクルードしてください。変数の宣言は次のように行います。

  1. tuple<データ型, ...> 変数名;
  2. 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 の引数がタプルの要素にコピーされます。引数が右辺値であれば、ムーブセマンティクスが働きます。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++のリファレンスマニュアルをお読みください。


初版 2015 年 11 月 28 日
改訂 2023 年 4 月 15 日

Copyright (C) 2015-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | C++ | NextPage ]