C++で文字列を切り出すときは、主に substr という機能を使います。
substr は、文字列の中から一部分だけを取り出すための機能です。
たとえば、文字列の先頭だけを取り出したり、途中から最後まで取り出したり、特定の範囲だけを取り出したりできます。
基本的な考え方は、次の2つです。
1つ目は、どこから切り出すかです。
2つ目は、何文字分切り出すかです。
C++では、文字列の位置は先頭から数えます。
ただし、先頭の位置は「1」ではなく「0」です。そのため、最初の文字は0番目、2文字目は1番目、3文字目は2番目というように数えます。
この「0から数える」というルールは、C++の文字列処理ではとても重要です。
文字列の位置は0から始まる
先頭の文字は0番目
C++では、文字列の位置番号を0から数えます。
たとえば、「Hello」という文字列がある場合、それぞれの文字の位置は次のようになります。
| 文字 | H | e | l | l | o |
|---|---|---|---|---|---|
| 位置 | 0 | 1 | 2 | 3 | 4 |
このように、先頭の「H」は0番目です。
そのため、「1番目から切り出す」と考えた場合、人間の感覚では先頭の文字を思い浮かべがちですが、C++では2文字目からの切り出しになります。
初心者が間違えやすいポイントなので、文字列を切り出すときは「位置は0から始まる」と覚えておくとよいです。
切り出しで指定するのは終了位置ではなく文字数
第2指定は「どこまで」ではなく「何文字」
C++で文字列を切り出すときに、特に間違えやすいのが第2指定の意味です。
文字列を切り出すときは、開始位置と文字数を指定します。
このとき、第2指定は「終了位置」ではありません。
たとえば、2番目から4番目までを取り出す、という意味ではなく、2番目から4文字分を取り出す、という意味になります。
つまり、考え方としては次のようになります。
| 指定する内容 | 意味 |
|---|---|
| 開始位置 | どこから切り出すか |
| 文字数 | そこから何文字取り出すか |
この違いを理解していないと、思ったより長く切り出されたり、逆に短く切り出されたりします。
途中から最後まで切り出すこともできる
文字数を省略すると末尾まで取得される
文字列を切り出すとき、文字数の指定を省略することもできます。
文字数を省略した場合は、指定した開始位置から文字列の最後までが切り出されます。
たとえば、文章の前半を取り除いて、ある位置以降だけを取り出したい場合に便利です。
メールアドレスのドメイン部分、URLの一部、ファイル名の拡張子などを取り出すときにも、この考え方はよく使われます。
よく使う文字列の切り出しパターン
先頭から一定文字数を取り出す
文字列の先頭から数文字だけを取り出す処理はよく使います。
たとえば、商品コードの先頭部分だけを分類用に使う場合や、文章の冒頭だけをプレビューとして表示する場合などです。
この場合は、開始位置を先頭にして、必要な文字数だけを指定します。
先頭から切り出す場合、開始位置は0になります。
途中から一定文字数を取り出す
文字列の途中から一定の長さだけ取り出す処理もよく使われます。
たとえば、固定長のID、日付文字列、コード番号などから、特定の部分だけを抜き出す場合です。
このときは、取り出したい部分の開始位置と、そこから何文字取り出すかを指定します。
注意点として、開始位置は0から数えるため、人間が見たときの「何文字目」とずれる場合があります。
途中から最後まで取り出す
ある位置から文字列の最後まで取り出したい場合は、文字数を指定しなくても処理できます。
たとえば、メールアドレスの「@」以降を取り出す場合や、URLの特定の区切り以降を取得する場合などです。
このような処理では、「区切り文字の位置を探す」「その次の位置から最後まで切り出す」という流れになります。
最後の数文字を取り出す
文字列の末尾から数文字を取り出す処理もよく使います。
たとえば、ファイルの拡張子、コードの末尾番号、日付の年・月・日などを取得するときです。
ただし、最後の数文字を取り出す場合は、文字列全体の長さを使って開始位置を計算する必要があります。
このとき、取り出したい文字数が文字列全体の長さより大きい場合には注意が必要です。
長さを超えて計算すると、意図しない動作やエラーにつながることがあります。
安全に処理するには、先に文字列の長さを確認してから切り出すのが基本です。
区切り文字と組み合わせて使う
特定の文字の位置を探してから切り出す
実際の文字列処理では、単純に「何番目から何文字」と決まっているケースばかりではありません。
たとえば、次のような文字列があります。
- メールアドレス
- ファイル名
- URL
- カンマ区切りのデータ
- コロンやスラッシュで区切られた文字列
このような文字列では、まず区切り文字の位置を探し、その位置を基準にして文字列を切り出します。
たとえば、メールアドレスであれば「@」の位置を探し、その前をユーザー名、その後ろをドメイン名として切り出すことができます。
見つからなかった場合の処理が重要
区切り文字を探してから切り出す場合、必ず考えるべきなのが「区切り文字が見つからなかった場合」です。
たとえば、メールアドレスから「@」を探す場合でも、入力された文字列に「@」が含まれていない可能性があります。
その状態で無理に切り出そうとすると、想定外の結果になったり、エラーの原因になったりします。
そのため、実用的な処理では、区切り文字が見つかったかどうかを確認してから切り出す必要があります。
ファイル拡張子を取り出す場合の注意点
最初のドットではなく最後のドットを見る方がよい
ファイル名から拡張子を取り出す処理は、文字列切り出しの代表的な例です。
ただし、ファイル名にはドットが複数含まれることがあります。
たとえば、圧縮ファイルやバックアップファイルでは、ファイル名の中に複数のドットが入っていることがあります。
このような場合、最初のドットを基準にすると、想定よりも長い文字列が拡張子として取得されてしまうことがあります。
一般的に、拡張子を取り出すときは、最後のドットを基準にする方が実用的です。
ドットがないファイル名にも対応する
ファイル名には、必ず拡張子があるとは限りません。
たとえば、READMEのようにドットがないファイル名もあります。
そのため、ファイル拡張子を取り出す処理では、ドットが存在するかを確認する必要があります。
ドットが見つからない場合は、「拡張子なし」として処理するのが自然です。
また、ファイル名がドットで終わっている場合も注意が必要です。
この場合、最後にドットはありますが、その後ろに文字がないため、拡張子が空になります。
このようなケースを許可するかどうかは、処理の目的によって変わります。
範囲外の指定に注意する
開始位置が長さを超えるとエラーになる
文字列を切り出すときに、開始位置が文字列の長さより大きいとエラーになります。
たとえば、5文字しかない文字列に対して、10番目から切り出そうとすると、存在しない位置を指定していることになります。
そのため、実用的な処理では、指定した開始位置が文字列の範囲内にあるかを確認することが大切です。
開始位置が文字列の長さと同じ場合は空文字になる
ここで注意したいのは、開始位置が文字列の長さと同じ場合です。
この場合は、エラーではありません。
文字列のちょうど末尾の位置から切り出すことになり、結果として空文字列が返ります。
つまり、エラーになるのは「開始位置が文字列の長さより大きい場合」です。
| 開始位置 | 結果 |
|---|---|
| 文字列の範囲内 | 指定位置から切り出される |
| 文字列の長さと同じ | 空文字列になる |
| 文字列の長さより大きい | エラーになる |
この違いは細かいですが、正確に理解しておくと安全なコードを書きやすくなります。
文字数を長く指定しすぎた場合
取り出す文字数が長すぎても基本的には問題ない
開始位置が範囲内であれば、取り出す文字数を実際の残り文字数より多く指定しても、通常は問題ありません。
その場合、指定された文字数に足りなくても、文字列の最後までが切り出されます。
たとえば、途中から100文字取り出すように指定しても、実際に残っている文字数が少なければ、末尾までの文字列が返されます。
つまり、注意すべきなのは主に開始位置です。
文字数が長すぎること自体は、多くの場合、大きな問題にはなりません。
文字列を分割する場合
カンマ区切りなどの処理にも使える
文字列の切り出しは、文字列の分割にも使えます。
たとえば、カンマで区切られた文字列を、1つずつ取り出す処理です。
この場合は、カンマの位置を探し、その手前までを切り出します。
次に、カンマの次の位置から再び検索し、同じ処理を繰り返します。
この方法を使えば、カンマ区切りのデータを1つずつ取り出すことができます。
空の要素が含まれる場合に注意する
カンマ区切りの文字列では、空の要素が含まれることがあります。
たとえば、カンマが連続している場合や、文字列の末尾がカンマで終わっている場合です。
このような場合、区切りと区切りの間に何も文字がないため、空文字列として扱われます。
空文字列も1つの要素として扱いたいのか、それとも無視したいのかは、処理の目的によって変わります。
たとえば、CSVのようなデータでは空欄にも意味がある場合があります。
一方、単純なリスト処理では、空の要素を無視したい場合もあります。
そのため、分割処理では空要素の扱いを事前に決めておくことが大切です。
元の文字列を変更する処理との違い
切り出しは元の文字列を変更しない
文字列を切り出す処理は、元の文字列を変更しません。
元の文字列から一部分を取り出して、新しい文字列として扱います。
そのため、切り出し後も元の文字列はそのまま残ります。
たとえば、文章の一部だけを表示したい場合や、元のデータを保持したまま一部分だけを別の処理に使いたい場合に便利です。
削除処理とは目的が違う
一方で、文字列の一部を削除する処理もあります。
削除処理は、元の文字列そのものを変更します。
つまり、切り出しと削除は似ているようで目的が違います。
| 処理 | 内容 |
|---|---|
| 切り出し | 元の文字列を残したまま、一部を取り出す |
| 削除 | 元の文字列から一部を取り除く |
元の文字列を残したい場合は切り出しを使い、元の文字列そのものを変えたい場合は削除処理を使います。
C言語風の文字列を扱う場合
char 配列にはそのまま使えない
C++では、文字列を扱う方法として、C言語風の文字列もあります。
ただし、C言語風の文字列には、C++の文字列用の切り出し機能をそのまま使うことはできません。
C++で文字列を扱う場合は、基本的に std::string を使う方が便利です。
C言語風の文字列を切り出したい場合は、いったんC++の文字列型に変換してから処理すると扱いやすくなります。
日本語文字列を切り出すときの注意点
std::string は基本的にバイト単位で扱う
日本語を含む文字列を扱う場合は、英数字だけの文字列とは違った注意が必要です。
C++の std::string は、基本的に文字を「見た目の1文字」ではなく、バイト単位で扱います。
UTF-8の日本語では、1文字が複数バイトで表現されることが一般的です。
そのため、日本語文字列を単純に途中で切り出すと、1文字の途中で切れてしまい、文字化けすることがあります。
日本語を1文字単位で扱うには別の対応が必要
日本語を安全に1文字単位で切り出したい場合は、単純な std::string の切り出しだけでは不十分なことがあります。
選択肢としては、Unicodeに対応した文字列型や、文字コード処理に対応したライブラリを使う方法があります。
ただし、Unicodeの扱いは少し複雑です。
特に、絵文字、濁点、結合文字などが含まれる場合、プログラム上の1文字と、ユーザーが見た目で認識する1文字が一致しないことがあります。
英数字中心の処理であれば通常の文字列切り出しで十分ですが、日本語や絵文字を本格的に扱う場合は、文字コードへの理解が必要です。
string_view を使う場合
コピーを避けたい場合に便利
C++17以降では、string_view という仕組みも使えます。
通常の文字列切り出しでは、取り出した部分が新しい文字列として作られます。
つまり、必要に応じて文字列のコピーが発生します。
一方、string_view は元の文字列の一部を参照するだけです。
そのため、大量の文字列を処理する場合や、コピーのコストを抑えたい場合に役立ちます。
元の文字列の寿命に注意する
string_view は便利ですが、注意点もあります。
string_view は文字列データを所有しません。
あくまで、どこかに存在している文字列を参照しているだけです。
そのため、元の文字列が消えてしまうと、string_view は無効な参照を持つことになります。
この状態で使うと、予期しない動作につながります。
考え方としては、次のように整理できます。
| 種類 | 特徴 |
|---|---|
std::string | 文字列データを所有する |
std::string_view | 文字列データを参照するだけ |
string_view を使う場合は、参照先の文字列がまだ有効であることを必ず意識する必要があります。
実務で意識したいポイント
入力値が想定どおりとは限らない
実務では、文字列が必ず期待どおりの形式になっているとは限りません。
- メールアドレスに「@」がないかもしれません。
- ファイル名に拡張子がないかもしれません。
- 区切り文字が複数あるかもしれません。
- 空文字列が渡されるかもしれません。
そのため、文字列を切り出す前には、必要な文字が存在するか、開始位置が範囲内か、空文字列ではないかなどを確認することが大切です。
固定位置で切るか、区切り文字で切るかを分けて考える
文字列の切り出しには、大きく2つの考え方があります。
1つは、固定位置で切り出す方法です。
もう1つは、区切り文字を探して切り出す方法です。
固定位置で切り出す方法は、文字列の形式が決まっている場合に向いています。
たとえば、日付、商品コード、固定長IDなどです。
一方、区切り文字で切り出す方法は、長さが変わる文字列に向いています。
たとえば、メールアドレス、URL、ファイル名、カンマ区切りデータなどです。
どちらの方法が適しているかは、扱う文字列の形式によって変わります。
まとめ
C++で文字列を切り出す基本は、開始位置と文字数を指定して一部分を取り出すことです。
特に重要なのは、位置が0から始まることと、第2指定が終了位置ではなく文字数であることです。
また、実用的な処理では、単に切り出すだけでなく、区切り文字が見つからなかった場合や、開始位置が範囲外になった場合にも対応する必要があります。
ファイル拡張子を取り出す場合は、最初のドットではなく最後のドットを基準にする方が実用的です。
日本語文字列を扱う場合は、std::string が基本的にバイト単位で処理される点にも注意が必要です。
英数字なら問題になりにくいですが、日本語や絵文字では文字化けや不自然な切り出しが起きる可能性があります。
最後に、C++17以降では string_view を使ってコピーを避ける方法もあります。
ただし、string_view は元の文字列を所有せず参照するだけなので、元の文字列の寿命に注意する必要があります。
C++の文字列切り出しでは、次のポイントを押さえておくと安全です。
| ポイント | 内容 |
|---|---|
| 位置は0始まり | 先頭は0番目 |
| 指定するのは文字数 | 終了位置ではない |
| 開始位置に注意 | 文字列の長さより大きいとエラー |
| 区切り文字は存在確認する | 見つからない場合を想定する |
| 拡張子は最後のドットを見る | 複数ドットのファイル名に対応しやすい |
| 日本語はバイト単位に注意 | 文字化けの可能性がある |
string_view は寿命管理が重要 | 参照先が消えると危険 |
このように整理すると、C++での文字列切り出しは、基本だけでなく実務上の注意点まで理解しやすくなります。
以上、C++での文字列の切り出し方についてでした。
最後までお読みいただき、ありがとうございました。
