C++のstd::stringの連結とは、複数の文字列をつなげて1つの文字列にする処理のことです。
たとえば、名前とメッセージをつなげたり、ファイルパスを組み立てたり、ログ出力用の文章を作ったりするときに使います。
C++では、std::stringを使うことで、C言語風の文字列よりも安全かつ扱いやすく文字列を操作できます。
std::stringの連結には、主に次のような方法があります。
+演算子を使う方法、+=演算子を使う方法、append()を使う方法、push_back()で1文字を追加する方法、insert()で途中に文字列を挿入する方法、std::ostringstreamやstd::formatで整形しながら文字列を作る方法などです。
それぞれ役割や向いている場面が異なるため、用途に合わせて使い分けることが大切です。
std::stringの基本的な連結方法
+演算子で文字列を連結する
std::stringの連結で最も直感的なのが、+演算子を使う方法です。
+演算子を使うと、2つ以上の文字列を足し合わせるような感覚で連結できます。
短い文字列を組み合わせて、1つのメッセージを作る場合に便利です。
たとえば、「Hello」と「World」をつなげて「Hello World」のような文字列を作る場合などに使います。
+演算子は読みやすく、コードの意図も分かりやすいため、短い文字列を数個連結する程度であれば非常に使いやすい方法です。
ただし、何度も繰り返し連結する処理や、大量の文字列を扱う処理では、一時的な文字列オブジェクトが作られやすくなるため、効率面に注意が必要です。
+=演算子で文字列を追加する
既存のstd::stringに対して、後ろから文字列を追加したい場合は、+=演算子が便利です。
+演算子が「新しい文字列を作る」イメージなのに対して、+=演算子は「今ある文字列に追加していく」イメージです。
たとえば、最初に空の文字列を用意しておき、そこに見出し、本文、改行、区切り文字などを順番に追加していくような場面でよく使われます。
特に、ループ処理の中で文字列を少しずつ組み立てる場合は、+よりも+=の方が意図が明確です。
大量の連結処理を行う場合にも、+=やappend()を使う方が実務では自然です。
append()で文字列を追加する
append()は、std::stringに用意されているメンバ関数で、文字列の末尾に別の文字列を追加するときに使います。
役割としては+=演算子に近いですが、append()の方が細かい指定に向いています。
たとえば、別の文字列の一部だけを追加したり、同じ文字を複数回追加したりするような処理では、append()が便利です。
単純な連結であれば+=で十分なことが多いですが、文字数や範囲を指定して追加したい場合には、append()を使うと分かりやすくなります。
push_back()で1文字だけ追加する
push_back()は、std::stringの末尾に1文字だけ追加するための関数です。
文字列全体ではなく、1文字だけを追加したい場合に使います。
たとえば、文字列の最後に改行文字を追加したり、区切り文字を1文字だけ追加したりする場合に便利です。
注意点として、push_back()で追加できるのは1文字です。
文字列そのものを追加する関数ではありません。
1文字を表す場合はシングルクォートで囲まれた文字を使います。
一方、ダブルクォートで囲まれたものは文字列リテラルとして扱われます。
この違いは初心者が間違えやすいポイントです。
insert()で文字列の途中に追加する
文字列の末尾ではなく、途中に別の文字列を入れたい場合はinsert()を使います。
append()や+=は基本的に末尾へ追加する操作ですが、insert()は指定した位置に文字列を挿入できます。
たとえば、「HelloWorld」という文字列の途中に空白を入れて「Hello World」にしたい場合などに使えます。
insert()では、どの位置に挿入するかを指定します。
C++の文字列の位置は0から数えるため、挿入位置を指定するときはインデックスの考え方に注意が必要です。
文字列リテラルとの連結で注意すべきこと
std::stringと文字列リテラルは連結できる
std::stringと文字列リテラルは、基本的に連結できます。
文字列リテラルとは、ダブルクォートで囲まれた文字列のことです。
たとえば、「Hello」や「World」のようなものです。
std::string型の文字列と、文字列リテラルを組み合わせる場合は、+演算子で連結できます。
そのため、std::stringの変数に対して、前後に固定のメッセージを付けるような処理は問題なく行えます。
文字列リテラル同士は+で連結できない
注意が必要なのは、文字列リテラル同士を+で連結しようとする場合です。
std::string同士、またはstd::stringと文字列リテラルであれば連結できますが、文字列リテラル同士をそのまま+でつなげることはできません。
これは、文字列リテラルがstd::stringではなく、C言語風の文字列として扱われるためです。
C++では、文字列リテラル同士に対して+演算子による文字列連結は行えません。
この場合は、どちらか一方をstd::stringとして扱う必要があります。
隣接する文字列リテラルは自動で結合される
文字列リテラル同士を連結したい場合、+を使わずに、文字列リテラルを並べて書く方法があります。
C++では、隣り合った文字列リテラルはコンパイル時に自動的に結合されます。
これは、実行時に文字列を連結するのではなく、コンパイルの段階で1つの文字列として扱われる仕組みです。
そのため、固定の長い文字列を複数行に分けて書きたい場合などに便利です。
"s"サフィックスを使う方法もある
C++14以降では、文字列リテラルにsサフィックスを付けることで、std::stringとして扱うことができます。
これにより、通常の文字列リテラルではなく、最初からstd::stringとして連結に使えるようになります。
ただし、この機能を使うには、std::string_literalsの名前空間を利用する必要があります。
便利な機能ですが、プロジェクトによっては名前空間の扱いにルールがある場合もあります。
そのため、使う場合は関数内など、なるべく狭い範囲で利用するとよいでしょう。
数値とstd::stringを連結するときの注意点
数値はそのまま連結しない
std::stringに数値を連結したい場合は、注意が必要です。
たとえば、年齢やスコア、個数、価格などの数値を文字列に含めたい場面はよくあります。
しかし、数値をそのままstd::stringに足すと、意図した結果にならない可能性があります。
特に注意したいのは、整数が文字として扱われる可能性がある点です。
C++では、std::stringに1文字を追加する仕組みがあります。
そのため、整数を渡したときに、それが数値の文字列ではなく、文字コードとして解釈される場合があります。
たとえば、数値の20を追加したつもりでも、文字列の「20」が追加されるとは限りません。
意図しない1文字が追加される可能性があります。
そのため、数値を文字列として連結したい場合は、明示的に文字列へ変換する必要があります。
std::to_string()を使う
数値をstd::stringに変換する代表的な方法がstd::to_string()です。
整数や浮動小数点数を文字列に変換したい場合に使えます。
たとえば、年齢、点数、個数、IDなどを文章の中に含めたい場合、数値をstd::to_string()で文字列にしてから連結します。
この方法を使えば、数値が意図しない文字として扱われる問題を避けられます。
小数の変換には注意する
std::to_string()で小数を文字列に変換する場合、小数点以下の桁数が想定より多くなることがあります。
多くの環境では、3.14のような値が「3.140000」のように変換される場合があります。
そのため、小数点以下の桁数をきれいに整えたい場合や、金額、割合、測定値などを表示したい場合は、std::to_string()だけでは不十分なことがあります。
このような場合は、std::ostringstreamやstd::formatを使うと、より細かく書式を指定できます。
std::ostringstreamを使った連結
数値や文字列を自然に組み合わせられる
std::ostringstreamは、文字列をストリームとして組み立てるための仕組みです。
文字列、整数、小数などを自然に並べて、最終的に1つのstd::stringとして取り出せます。
std::to_string()を何度も書かなくてよいため、複数の数値や文字列を組み合わせる場合に便利です。
たとえば、ユーザー名、年齢、身長、スコア、日時などを含むメッセージを作る場合に向いています。
小数点以下の桁数を指定しやすい
std::ostringstreamでは、小数点以下の桁数を指定することもできます。
たとえば、価格を小数点以下2桁で表示したい場合や、割合を一定の桁数で表示したい場合に便利です。
std::to_string()では出力形式を細かく制御しにくいですが、std::ostringstreamを使えば、表示形式を調整しながら文字列を作れます。
そのため、数値の見た目を整えたい場合は、std::ostringstreamが有力な選択肢になります。
std::formatを使った連結
C++20以降で使える読みやすい方法
C++20以降では、std::formatを使って文字列を組み立てることもできます。
std::formatは、文字列の中に値を埋め込む形で記述できるため、複雑な連結よりも読みやすくなります。
文字列、数値、小数などをまとめて扱いやすく、メッセージ生成やログ出力などにも向いています。
たとえば、「名前」「年齢」「スコア」のような複数の値を1つの文章に整形する場合、std::formatを使うと簡潔に書けます。
環境によって対応状況に注意する
std::formatは便利ですが、C++20以降の機能です。
また、コンパイラや標準ライブラリの対応状況によっては、C++20を指定していても使えない場合があります。
特に古いコンパイラ環境では、std::formatが未対応だったり、部分的な対応にとどまっていたりすることがあります。
そのため、実務で使う場合は、使用しているコンパイラやビルド環境がstd::formatに対応しているかを確認する必要があります。
対応していない場合は、std::ostringstreamや外部ライブラリのfmtを検討するとよいでしょう。
std::string_viewとの連結
std::string_viewは文字列を所有しない
C++17以降では、std::string_viewという型が使えます。
std::string_viewは、文字列を所有せず、既存の文字列を参照するための軽量な型です。
文字列のコピーを避けたい場合や、関数の引数として文字列を受け取りたい場合によく使われます。
ただし、std::string_viewは文字列そのものを所有しているわけではありません。
そのため、連結結果を保持したい場合は、最終的にstd::stringなどの実体を持つ文字列を作る必要があります。
std::string_view同士はそのまま+で連結できない
std::string_view同士をそのまま+で連結することはできません。
これは、std::string_viewがあくまで文字列を参照するための型であり、新しい文字列を作るための型ではないためです。
連結した結果を保持したい場合は、新しいstd::stringを用意して、そこに内容を追加していくのが一般的です。
大量の連結が必要な場合は、あらかじめ必要なサイズを見積もって容量を確保してから追加すると効率的です。
日本語文字列の連結
std::stringでも日本語は扱える
std::stringはバイト列として文字列を扱います。
そのため、UTF-8の日本語文字列も格納できます。
日本語の文章同士を連結して、1つの文字列にすることも可能です。
たとえば、「こんにちは」と「世界」を連結して、「こんにちは世界」のような文字列を作ることはできます。
文字数とバイト数の違いに注意する
日本語文字列を扱う場合に注意したいのは、std::stringのサイズは文字数ではなく、基本的にはバイト数として扱われるという点です。
UTF-8では、日本語1文字が複数バイトで表現されることがあります。
そのため、見た目では1文字でも、std::stringのサイズとしては3バイトなどになる場合があります。
単純に日本語を連結するだけなら大きな問題はありませんが、文字数を数えたり、途中で切り出したり、1文字ずつ処理したりする場合には注意が必要です。
日本語を文字単位で正確に扱いたい場合は、文字コードやUnicodeに対応した処理を検討する必要があります。
大量に連結する場合のパフォーマンス
短い連結なら+で十分
短い文字列を数個連結するだけであれば、+演算子で問題ありません。
たとえば、姓と名をつなげる、短いメッセージを作る、固定の接頭辞や接尾辞を付けるといった処理であれば、読みやすさを優先してよいでしょう。
現代のC++コンパイラや標準ライブラリでは、短い文字列の連結に対してさまざまな最適化が働くこともあります。
そのため、少量の連結では、無理に複雑な書き方をする必要はありません。
ループ内では+=やappend()を使う
一方で、ループの中で何度も文字列を連結する場合は、+演算子の多用に注意が必要です。
特に、毎回「今ある文字列」と「追加したい文字列」を足して新しい文字列を作るような書き方は、一時オブジェクトやコピーが増えやすくなります。
このような場合は、既存の文字列に追加していく+=やappend()を使う方が適しています。
ログ、CSV、HTML、SQL、テンプレート文字列などを大量に組み立てる場合は、連結方法によって処理効率に差が出ることがあります。
reserve()で容量を事前に確保する
大量の文字列を連結する場合は、reserve()を使って容量をあらかじめ確保すると効率がよくなることがあります。
std::stringは、内部に文字列を保存するためのメモリ領域を持っています。
文字列を追加して容量が足りなくなると、新しい領域を確保し、既存の内容をコピーする必要があります。
この再確保が何度も発生すると、処理が遅くなる原因になります。
そこで、最終的な文字列の長さがある程度予想できる場合は、先に容量を確保しておくと、再確保の回数を減らせる可能性があります。
ただし、reserve()を使えば必ず速くなるわけではありません。
実際の効果は、文字列の長さ、連結回数、標準ライブラリの実装、コンパイラの最適化などによって変わります。
そのため、性能が重要な場面では、実際に計測して判断することが大切です。
std::stringの連結でよくある間違い
文字列リテラル同士を+でつなげようとする
よくある間違いの1つが、文字列リテラル同士を+で連結しようとすることです。
std::string同士であれば+で連結できますが、ダブルクォートで囲まれた文字列リテラル同士は、そのままではstd::stringではありません。
そのため、文字列リテラル同士を+で足すような書き方は避ける必要があります。
この場合は、どちらか一方をstd::stringとして扱うか、文字列リテラルを隣接させてコンパイル時に結合させる方法を使います。
数値をそのまま連結しようとする
数値を文字列に含めたい場合、数値をそのまま足してはいけません。
整数をそのままstd::stringに追加しようとすると、数値の文字列表現ではなく、1文字として扱われる可能性があります。
その結果、期待していた「100」や「20」といった文字列ではなく、意図しない文字が追加されることがあります。
数値を文字列として連結したい場合は、std::to_string()、std::ostringstream、std::formatなどを使って、明示的に文字列化することが重要です。
1文字と文字列を混同する
C++では、1文字と文字列は異なります。
シングルクォートで囲まれたものは1文字です。
一方、ダブルクォートで囲まれたものは文字列リテラルです。
たとえば、1文字だけを追加する場合は、1文字として扱う必要があります。
一方、複数文字や文字列を追加する場合は、文字列として扱います。
この違いを理解しておかないと、push_back()に文字列を渡してしまったり、文字列のつもりで1文字を扱ってしまったりするミスにつながります。
+の評価順を誤解する
複数の文字列を+で連結するときは、左から順に評価されます。
最初にstd::stringが含まれていれば、その後の連結はstd::stringとして処理されやすくなります。
しかし、最初の部分が文字列リテラル同士の連結になっている場合は、そこでエラーになります。
つまり、式全体のどこかにstd::stringがあれば必ず大丈夫、というわけではありません。
最初にどの型同士が連結されるかが重要です。
この問題を避けるには、最初の文字列を明示的にstd::stringとして扱うか、C++14以降であればsサフィックスを使う方法があります。
実務での使い分け
短いメッセージなら+を使う
短い文字列を読みやすく連結したい場合は、+演算子が適しています。
名前、短いメッセージ、ラベル、ファイル名の一部などを組み合わせる程度であれば、+で十分です。
可読性が高く、直感的に理解しやすいのがメリットです。
追加しながら組み立てるなら+=を使う
段階的に文字列を作っていく場合は、+=が向いています。
たとえば、最初に空の文字列を用意し、そこへ見出し、本文、改行、区切り文字などを順番に追加していく場合です。
処理の流れが分かりやすく、ループ処理とも相性がよい方法です。
細かく制御したいならappend()を使う
文字列の一部だけを追加したい場合や、同じ文字を複数回追加したい場合は、append()が便利です。
+=よりも細かい指定ができるため、文字列操作を明確に書きたいときに向いています。
単純な追加なら+=、範囲指定や繰り返し追加が必要ならappend()という使い分けが分かりやすいでしょう。
1文字だけならpush_back()を使う
1文字だけを末尾に追加したい場合は、push_back()を使います。
改行、カンマ、空白、区切り文字など、1文字だけを追加する処理に向いています。
ただし、文字列全体を追加する用途には使いません。
数値や書式が多いならstd::ostringstreamやstd::formatを使う
数値を含むメッセージや、表示形式を整えたい文字列を作る場合は、std::ostringstreamやstd::formatが向いています。
std::ostringstreamは、古めの環境でも使いやすく、小数点以下の桁数なども調整できます。
std::formatはC++20以降で使える機能で、より読みやすく整形できます。
ただし、std::formatは環境によって対応状況に差があるため、使用前に確認が必要です。
大量連結ならreserve()を検討する
大量の文字列を連結する場合は、reserve()で容量を事前に確保することを検討します。
特に、ログ生成、CSV生成、HTML生成、大きなテキストデータの組み立てなどでは、メモリ再確保の回数を減らすことで効率がよくなる可能性があります。
ただし、最適化の効果は状況によって異なります。
重要な処理では、実際に速度を測って判断することが大切です。
std::stringの連結で押さえるべきポイント
std::string同士の連結は基本的に簡単
std::string同士であれば、+演算子や+=演算子を使って簡単に連結できます。
短い文字列をつなげるだけなら、難しく考える必要はありません。
まずは、+で新しい文字列を作る、+=で既存の文字列に追加する、という違いを理解するとよいでしょう。
文字列リテラル同士の+には注意する
文字列リテラル同士は、そのまま+で連結できません。
この点は、std::stringを学び始めたときに混乱しやすい部分です。
どちらか一方をstd::stringとして扱うか、隣接する文字列リテラルの自動結合を使う必要があります。
数値は必ず文字列化してから連結する
数値を文字列に含めたい場合は、必ず文字列化してから連結します。
整数をそのまま足すと、数値の文字列表現ではなく、1文字として扱われる可能性があります。
これは非常に誤解しやすく、バグの原因にもなりやすいポイントです。
数値を含む文字列を作る場合は、std::to_string()、std::ostringstream、std::formatのいずれかを使うとよいでしょう。
大量処理では効率を意識する
少量の文字列連結であれば、可読性を優先して問題ありません。
しかし、大量の文字列をループ内で連結する場合は、一時オブジェクトやメモリ再確保が増えないように注意が必要です。
そのような場面では、+=やappend()を使い、必要に応じてreserve()で容量を確保するのが基本です。
まとめ
C++のstd::stringの連結には、さまざまな方法があります。
短い文字列を分かりやすくつなげたい場合は、+演算子が便利です。
既存の文字列に順番に追加していく場合は、+=演算子やappend()が適しています。
1文字だけ追加するならpush_back()、途中に挿入したいならinsert()を使います。
数値を含む文字列を作る場合は、数値をそのまま連結しないことが重要です。
整数をそのまま追加すると、数値の文字列ではなく、1文字として扱われる可能性があります。
数値を文字列として扱いたい場合は、std::to_string()、std::ostringstream、std::formatなどを使って明示的に変換します。
また、大量の文字列を連結する場合は、reserve()を使って容量を事前に確保すると、再確保の回数を減らせる可能性があります。
実務では、次のように使い分けると分かりやすいです。
短い連結なら+、段階的な追加なら+=、細かい制御が必要ならappend()、1文字だけならpush_back()、数値や書式が多いならstd::ostringstreamまたはstd::format、大量連結ならreserve()を検討する、という考え方です。
std::stringの連結は一見シンプルですが、文字列リテラル、数値、1文字、std::string_viewなどが関わると注意点が増えます。
基本的な使い分けと落とし穴を理解しておくことで、安全で読みやすいC++コードを書きやすくなります。
以上、C++のstd::stringの連結についてでした。
最後までお読みいただき、ありがとうございました。
