string型

概要

文字列型。
自由な文字数の文字列を入れることができる。
ダブルクオーテーション " を使うことで string 型として扱われる。

$1$ つの変数のようにも扱えるが、実際の中身は char 型変数の集合体である。
コードを書くときは $2$ 種類の見方を上手に切り替えながら行う必要がある。

宣言と初期化

宣言だけする場合

string s;  // 宣言だけで実は空文字列 "" で初期化される

代入で初期化する場合

string s = "Hello World!";  // 固定値で初期化
string s = string(5,'a');   // 文字数と文字種で初期化、この場合は "aaaaa" となる
string s = t;               // 他の変数(文字列型)で初期化

(コピー)コンストラクタで初期化する場合

string s("Hello World!");  // 固定値で初期化
string s(5,'a');           // 文字数と文字種で初期化、この場合は "aaaaa" となる
string s(t);               // 他の変数(文字列型)で初期化

可能な演算

基本的なもの

$i$ 文字目を参照 .at(i) または [i]

例えば、

s.at(3)

と書けば、「s という文字列の $3$ 文字目」という char 型変数であるかのように扱える。
ただし、コンピューター的には文字数は $0$ から始まることに注意。
"abcde" の $3$ 文字目は 'd' である。
以後の計算でも全て同様。

s[3]

と書いてもほぼ同じ。
違いは、範囲外($3$ 文字しかないのに $5$ 文字目を参照しようとする)などしたときの挙動の違い。

前者はエラーしてくれるのでバグの修正がしやすいが、コードがちょっと見にくい。
後者はちょっとコードが見やすいが、範囲外アクセスが未定義動作になるのでバグの修正が大変。
急いでバグ修正する必要がある競プロでは .at(i) を使う方が無難。

長さの取得 .size() または .length()

s.size()

で文字列 s の長さを size_t 型で取得できる。
size_t 型は扱いの注意点も多いので、取得したら int 型変数に一度代入してしまうのも $1$ つの手。
C++20以降なら、ssize(s) で直接に符号付きで長さを取得することもできる。

代入 =

s=t; で、変数 s の中身を t に書き換える。
数学とは違い、左右を逆にして t=s; と書くと意味が変わるので注意。

連結 +

s+t で、st を連結する。
tchar 型でもよい。
複合代入演算子 += も使える。

空判定 .empty()

s.empty() で、s が空っぽかどうかを bool 型で返す。
結果自体は s==""s.size()==0 と書くのと変わらない。
一応、.empty() が最も高速らしいが、誤差レベルなので書きやすさ優先で選んでよい。

一致判定 ==

s==t で、文字列が一致するか判定する。
すなわち、長さが同じで、各位置の文字もすべて一致するか判定する。
!= も使える。

辞書順判定 <

s<t で、辞書順で st より前かどうかを判定する。
すなわち、以下のように判定する。

逆向きの > も使える。
<=>= も使える。

コンストラクタ string(文字数,文字)(B問題相当)

string(5,'a') と書くと、'a' を $5$ 文字並べた文字列 "aaaaa" を意味する。
$5$ 文字くらいなら直接書いた方が早いが、$100$ 文字並べる場合などにはコンストラクタで書いた方が便利。

メモリ確保 .reserve(文字数)(C問題相当)

s.reserve(100000) と書くと、$100000$ 文字分のメモリを確保する。
確保できない場合は、今のうちに $100000$ 文字確保できるところにデータをお引越ししてくれる。
後で文字をどんどん追加して長くなっていく場合に、可能性のある最長文字数以上に確保しておくとよい。

別にこれをやらなくても、よしなに引越ししてくれるため、必須ではない。
とはいえ、長い文字列の引越しが発生したせいでTLEしたら悲しい。

文字列編集系(全体的にB問題相当)

先頭参照 .front()

s.front() で先頭の文字を char 型で参照する。
……普通に s.at(0) でいい気も。

末尾参照 .back()

s.back() で末尾の文字を char 型で参照する。
文字列の長さが途中で変化するような場合には便利。

部分文字列取得 .substr(開始位置,文字数) または string(イテレータ1,イテレータ2)

s.substr(4,2) なら、$4$ 文字目からの $2$ 文字を別の文字列として取得する。
参照ではないので、substr() したものを変更しても、元の文字列には何も起こらない。
string(s.begin()+4,s.begin()+6) でも同じことができる。

末尾挿入 .push_back(文字)

s.push_back('x'); なら文字列の最後に 'x' を $1$ 文字追加する。
……普通に +='x' でいい気が。
また、vector とは異なり .emplace_back() は使えない。

末尾削除 .pop_back()

s.pop_back(); で、s の最後の $1$ 文字を削除する。

途中挿入 .insert(イテレータ,文字)

s.insert(itr,'a'); で、itr の位置に 'a' という文字を挿入する。

途中削除 .erase(開始位置,文字数) または .erase(イテレータ)

s.erase(4,2); なら、$4$ 文字目からの $2$ 文字を削除する。
s.erase(4) のように文字数を省略すれば、$4$ 文字目から後ろ全てを削除する。
引数をイテレータにした場合は、その場所の文字を削除する。

全削除 .clear()

s.clear();s を空文字列にする。
……普通に s=""; でいい気も。

置換 .replace(開始位置,文字数,置換文字列)

s.replace(3,2,"abc"); なら、$3$ 文字目から $2$ 文字を削除し、代わりに "abc" をそこに入れる。

サイズ変更 .resize(文字数,文字)

文字列の長さを変更する。
短くする場合は後ろを削除し、長くする場合は後ろを指定文字で埋める。
例えば s の中身が "abcde" である場合、以下のようになる。

交換 .swap(別の文字列変数)

s.swap(t); で、st の中身が入れ替わる。
swap(s,t); でも同じ。
実は中身ではなくデータに紐づく変数名などの情報の方を入れ替えるので、中身が長くても一瞬で終わる。

検索系(全体的にB問題相当)

文字列検索 .find(文字列,開始位置)

s.find("abc",3) と書けば、s の中で $3$ 文字目以降に初めて "abc" という並びがある場所を検索する。
返り値は「該当場所の先頭が何文字目か」で、存在しなかった場合は size_t 型の最大値になる。
使いにくいので、一度 int 型に入れて -1 として扱わせるとよい。
s.find("abc") と開始位置を省略した場合は先頭から検索する。

文字列逆順検索 .rfind(文字列,開始位置)

s.rfind("abc",3) と書けば、s の中で $3$ 文字目以前に最後に "abc" という並びがある場所を検索する。
返り値は「該当場所の先頭が何文字目か」で、存在しなかった場合は size_t 型の最大値。
s.rfind("abc") と開始位置を省略した場合は末尾から検索する。

先頭一致判定 .starts_with(文字または文字列)

s.starts_with("abc") と書くと、s"abc" で始まるか判定し、bool 型で返してくれる。

末尾一致判定 .ends_with(文字または文字列)

s.ends_with("abc") と書くと、s"abc" で終わるか判定し、bool 型で返してくれる。

中間一致判定 .contains(文字または文字列)

s.contains("abc") と書くと、s"abc" を含むか判定し、bool 型で返してくれる。

該当文字数カウント count(イテレータ,イテレータ,要素)

count(s.begin(),s.end(),'a') と書けば、s 内に 'a' という文字が何個あるかを数える。

よく使う処理

中身を逆順に並べなおす

reverse(s.begin(),s.end());

文字列が逆順の方が都合がよい場合などに。

回文判定

逆順にしても元と同じ、をそのとおりにやればよい。
元の文字列を残す必要があるので、コピーしてから逆順にすること。

string t = s;
reverse(t.begin(),t.end());
if (s==t) {
  // 回文だった場合のコード
}

中身をアルファベット順に並べる

sort(s.begin(),s.end());

文字の並び順はもはや壊れてもよいので、中の文字を全部アルファベット順に並べてほしい場合に。

sort(s.rbegin(),s.rend());

なら、逆順に並ぶ。

アナグラム判定

st がアナグラムになっているか、つまり文字の順番を入れ替えて一致させられるかを判定する場合。
st を両方「中身をアルファベット順に並べる」をし、結果が一致するか見ればよい。

sort(s.begin(),s.end());
sort(t.begin(),t.end());
if (s==t) {
  // アナグラムだった場合のコード
}

st の中身が壊れたら困る場合は、事前に文字列をコピーし、そちらを判定に使うこと。

string str1 = s;
string str2 = t;
sort(str1.begin(),str1.end());
sort(str2.begin(),str2.end());
if (str1==str2) {
  // アナグラムだった場合のコード
}

$1$ 文字ずつ何か処理する

.at(i) で $1$ 文字ずつ見ていく。
長さ $n$ が与えられている場合。

for (int i=0; i<n; i++) {
  // 中身の文字 s.at(i) に対してしたいことを書く
}

長さ $n$ が与えられていない場合。
一度 .size() で長さを取得してから上のものを用いてもよい。

for (size_t i=0; i<s.size(); i++) {
  // 中身の文字 s.at(i) に対してしたいことを書く
}

$1$ 文字ずつ何か処理する、その2(B問題相当)

範囲for文で $1$ 文字ずつ取り出すことができる。

for (char c : s) {
  // 中身の文字 c に対してしたいことを書く
}

中身の書き換えまでしたい場合は char& として参照渡しをする。

文字ごとの出現回数を全種カウント(B問題相当)

文字列を見て、'a' が何回、'b' が何回、'c' が何回、(中略)、'z' が何回、とカウントする方法。

vector<int> counters(26);
for (int i=0; i<n; i++) {
  counters.at(s.at(i)-'a')++;
}

counters.at(0)'a' の個数、.at(1)'b' の個数、(中略)、.at(25)'z' の個数が入る。
コピペする場合、ループの終了条件の n の部分を s の文字数を表すものに書き換えること。
あるいは、範囲for文が使いこなせるのであれば、下のものの方が使いやすい。

vector<int> counters(26);
for (char c : s) {
  counters.at(c-'a')++;
}

指定文字で分割(B問題相当)

例えば "abc,de,fghi" という文字列を ',' で分割して vector<string> に変更したい場合。

vector<string> parts(1,"");
for (char c : s) {
  if (c==',') parts.emplace_back("");
  else parts.back() += c;
}

if文の中を変更すれば、他の文字での分割もできる。

数値化(B問題相当)

文字列型を int 型にしたい場合、以下のようにする。
ただし、そもそも int 型に収まるような数値かどうかを確認すること。

stoi(s)

同様に stoll()long long 型に、stod()double 型にできる。

逆に、int 型の数である a を文字列型に変換したい場合、以下のようにすればよい。

to_string(a)

注意点

処理速度に注意(C問題相当)

長さ $n$ の文字列は、それ自体 $1$ つの変数のように扱えるが、実際の中身は長さが $n$ の vector<char> に近い。
つまり、中身を $1$ 文字ずつ見ていく系の処理や、先頭や途中の文字が増減する処理は、$O(n)$ かかる。
コードの見た目で処理時間を見積もると、わりと痛い目を見るので注意。

関連アルゴリズム

char型

string 型の各文字は、char 型として扱う。

size_t型

.size().find() などの返り値には、size_t 型が使われる。

vector

文字を並べて保持し、添字アクセスや末尾への追加・削除などができる点で似ている。

イテレータ

.begin().end() でイテレータを取得し、範囲の指定などに用いる。

回文判定

文字列を逆順にしたものと元の文字列を比較することで、回文かどうかを判定できる。