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 で、s に t を連結する。
t は char 型でもよい。
複合代入演算子 += も使える。
空判定 .empty()
s.empty() で、s が空っぽかどうかを bool 型で返す。
結果自体は s=="" や s.size()==0 と書くのと変わらない。
一応、.empty() が最も高速らしいが、誤差レベルなので書きやすさ優先で選んでよい。
一致判定 ==
s==t で、文字列が一致するか判定する。
すなわち、長さが同じで、各位置の文字もすべて一致するか判定する。
!= も使える。
辞書順判定 <
s<t で、辞書順で s が t より前かどうかを判定する。
すなわち、以下のように判定する。
- 前から順に、初めて同じ位置に異なる文字があるところを探す
- その異なった場所で
sの方が前であるかを判定する。
- 前であるとは、ASCIIコードが小さいか、文字列がもう終了していること
逆向きの > も使える。
<= や >= も使える。
コンストラクタ 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" である場合、以下のようになる。
s.resize(3)なら"abc"になるs.resize(8,'x')なら"abcdexxx"になる
交換 .swap(別の文字列変数)
s.swap(t); で、s と t の中身が入れ替わる。
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());
なら、逆順に並ぶ。
アナグラム判定
s と t がアナグラムになっているか、つまり文字の順番を入れ替えて一致させられるかを判定する場合。
s と t を両方「中身をアルファベット順に並べる」をし、結果が一致するか見ればよい。
sort(s.begin(),s.end());
sort(t.begin(),t.end());
if (s==t) {
// アナグラムだった場合のコード
}
s や t の中身が壊れたら困る場合は、事前に文字列をコピーし、そちらを判定に使うこと。
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() でイテレータを取得し、範囲の指定などに用いる。
回文判定
文字列を逆順にしたものと元の文字列を比較することで、回文かどうかを判定できる。