std::string: 自分自身を管理するテキスト
前のページでは、スマートポインタによってオブジェクトがリソースを所有し、自動的に後始末する仕組みを見ました。std::string はその同じ考え方をテキストに当てはめたものです。文字のバッファを所有し、追加するとそれを伸ばし、文字列がスコープを抜けると解放します。new を呼ぶことも、バイト数を数えることも、ヌル終端の抜けを心配することもありません。
使うには <string> をインクルードします。このクラスは std 名前空間にあります。
この + 演算子は実際の仕事をしています。両方の断片に十分な大きさの新しいバッファを確保し、そこへコピーするのです。生の char* なら strcpy や strcat に手を伸ばし、バッファが十分大きいことを祈るはめになります。std::string はその種のミスを丸ごと消し去ります。
文字列の組み立てと連結
+ で連結できますが、文字列をその場で伸ばす主力は += です。毎回まったく新しい文字列を作るのではなく既存のバッファに追記するので、ループの中では特に重要です。
よくある意外な落とし穴: + は少なくとも一方のオペランドがすでに std::string である必要があります。2 つの文字列リテラルは単なる const char* なので、"a" + "b" はコンパイルできません - 2 つの生のポインタに対する operator+ は存在しないからです。まずどちらか一方を文字列にしましょう。
string s = "a" + "b"; // error: can't add two const char*
string s = string("a") + "b"; // fine - left side is a std::string
string s = "a"s + "b"; // fine in C++14+, the "s" literal suffix
最後の形は <string> の s サフィックス(using namespace std::string_literals;)を使っていて、リテラルを直接 std::string に変えることに注意してください。
文字列の中身にアクセスする
std::string は char のコンテナのように振る舞うので、添字でアクセスしたり、走査したり、一部分を取り出したりできます。
substr(pos, len) は元から複製したまったく新しい文字列を返し、元の文字列を変更しません。範囲には注意してください。5 文字の文字列に対する word[10] は未定義動作です。例外は投げられず、ただゴミを読み出すだけです。std::out_of_range を投げる、境界チェック付きのアクセスが欲しいなら、word[10] ではなく word.at(10) を使いましょう。
もう一つの定番の罠: .size() は符号なしの型(size_t)を返します。for (size_t i = word.size() - 1; i >= 0; --i) のような逆向きループは決して終わりません。符号なしの i は決して 0 を下回れず、ラップアラウンドして巨大な数になるからです。文字列を逆順にたどる必要があるときは、符号付きのインデックスを使うか、条件を組み直してください。
検索と置換
find は部分文字列や文字を探し、その開始インデックスを返します。対象が見つからないときは特別な定数 std::string::npos を返します。find が -1 を返すと思い込まず、必ずこれと比較してください。
テキストをその場で変更するには、replace(pos, len, text) がある範囲を新しい内容(長さが違っていてもよい)に置き換え、insert/erase が断片を追加・削除します。
文字列と数値
テキストと数値はひとりでに変換されません - "42" は整数の 42 ではなく 3 つの文字です。標準ライブラリは両方向の変換関数を用意しています。テキストを数値に解析するには stoi、stod などを、逆向きには to_string を使います。
ここには落とし穴が 2 つあります。1 つ目、stoi("abc") は std::invalid_argument を投げるので、信頼できない入力は try/catch で守りましょう。2 つ目、to_string(3.99) は 3.990000 という丸ごとの値を返します。精度や書式を制御したい場合は、それは文字列ストリーム(string streams)の仕事であり、まさに次に向かう先です。
try {
int n = std::stoi(userInput);
} catch (const std::invalid_argument&) {
std::cout << "That wasn't a number.\n";
}
次へ: 入力と出力
ここまでずっと cout で文字列を出力してきましたが、それをユーザーから読み戻すには独自の意外な落とし穴があります - cin >> name は最初の空白で止まるので、"Ada Lovelace" のような行全体には代わりに std::getline が必要です。次のページでは C++ の入力と出力をきちんと扱います。ストリーム演算子、行全体の読み取り、そして残った改行につまずかずに >> と getline を混在させる方法です。
よくある質問
C++ における std::string と char* の違いは何ですか?
std::string は自身のメモリを所有して管理し、必要に応じて伸長し、自動的に後始末を行うクラスです。char* は終端の '\0' を持つ、文字への生のポインタにすぎません。バッファ、長さ、寿命はすべて自分で管理することになります。ほぼすべての場面で std::string を選びましょう。バッファオーバーフローやダングリングポインタといったバグのカテゴリ全体を取り除いてくれます。
C++ で文字列の長さを取得するにはどうすればよいですか?
std::string に対して .size() または別名の .length() を呼び出します。name.size() は文字数を size_t として返します。どちらも同じ値を返しますが、他のすべての STL コンテナと揃うため size() のほうが一般的なスタイルです。
C++ で文字列を数値に変換するにはどうすればよいですか?
int には std::stoi、double には std::stod、その他にも stol、stof などを使います。例: int n = std::stoi("42");。これらはテキストが数値でない場合に std::invalid_argument を投げるので、入力が信頼できないときは try/catch で囲みましょう。