C++の文字列2
stringクラスの続き
文字列の挿入
文字列の任意の位置に別の文字列を挿入するにはinsert
関数を使用します。
#include <iostream>
#include <string>
int main()
{
using namespace std;
string str1 = "ABCDEFG";
string str2 = "abc";
//2文字目にstr1を挿入
str2.insert(2, str1);
cout << str2 << endl;
//abABCDEFGc
str2 = "abc";
//2文字目にstr1の先頭から2文字以降を挿入
str2.insert(2, str1, 2);
cout << str2 << endl;
//abCDEFGc
str2 = "abc";
//2文字目にstr1の2文字目から3文字分を挿入
str2.insert(2, str1, 2, 3);
cout << str2 << endl;
//abCDEc
str2 = "abc";
//2文字目にstr1の2文字目から4文字手前までを挿入(範囲指定)
str2.insert(str2.begin() + 2, &str1[2], &str1[4]);
cout << str2 << endl;
//abCDc
str2 = "abc";
//2文字目に5個のxを挿入
str2.insert(2, 5, 'x');
cout << str2 << endl;
//abxxxxxc
cin.get();
}
挿入はC言語では面倒な処理ですが、stringクラスならば簡単に実現できます。
基本的にassign関数などと同じですが、範囲指定挿入だけは第一引数が少し特殊な形式になっています。
これはイテレータというものですが、これについては別項で詳しく説明します。
とりあえず今は「こういうもの」と考えておいてください。
文字列の置き換え
文字列の置き換えにはreplace
関数を使用します。
#include <iostream>
#include <string>
int main()
{
using namespace std;
string str1 = "ABCDEFG";
string str2 = "abcdefg";
//2文字目から3文字をstr1で置き換え
str2.replace(2, 3, str1);
cout << str2 << endl;
//abABCDEFGfg
str2 = "abcdefg";
//2文字目から3文字をstr1の1文字目以降で置き換え
str2.replace(2, 3, str1, 1);
cout << str2 << endl;
//abBCDEFGfg
str2 = "abcdefg";
//2文字目から3文字をstr1の1文字目から4文字分で置き換え
str2.replace(2, 3, str1, 1, 4);
cout << str2 << endl;
//abBCDEfg
str2 = "abcdefg";
//2文字目~4文字目直前までをstr1で置き換え
str2.replace(str2.begin() + 2, str2.begin() + 4, str1);
cout << str2 << endl;
//abABCDEFGefg
str2 = "abcdefg";
//2文字目~4文字目直前までをstr1の1文字目~4文字目直前までで置き換え
str2.replace(str2.begin() + 2, str2.begin() + 4, &str1[1], &str1[4]);
cout << str2 << endl;
//abBCDefg
str2 = "abcdefg";
//2文字目から3文字を5個のxで置き換え
str2.replace(2, 3, 5, 'x');
cout << str2 << endl;
//abxxxxxfg
str2 = "abcdefg";
//2文字~4文字目直前までを5個のxで置き換え
str2.replace(str2.begin() + 2, str2.begin() + 4, 5, 'x');
cout << str2 << endl;
//abxxxxxefg
cin.get();
}
かなり数が多いですが、全てを覚える必要はありません。
色々な置き換えが出来るということだけ覚えておいて、「この置き換えは簡単に書けないかな」と思ったらその都度調べると良いでしょう。
部分文字列の取得
文字列から文字列の一部を取り出すにはsubstr
関数を使用します。
#include <iostream>
#include <string>
int main()
{
using namespace std;
string str = "ABCDEFG";
//3文字目から末尾までの文字列を取得
string r1 = str.substr(3);
cout << r1 << endl;
//DEFG
//3文字目から2文字分の文字列を取得
string r2 = str.substr(3, 2);
cout << r2 << endl;
//DE
cin.get();
}
先頭文字/末尾文字へのアクセス
先頭の文字はfront
関数でアクセスできます。
末尾の文字はback
関数でアクセスできます。
これらはC++11以降で使用可能です。
#include <iostream>
#include <string>
int main()
{
std::string s = "abc";
std::cout << s.front() << std::endl;
std::cout << s.back() << std::endl;
//書き換えも可能
s.back() = 'Z';
std::cout << s << std::endl;
std::cin.get();
}
a c abZ
front
関数はs[0]
と同じです。
back
関数はs[s.size() - 1]
と同じですが、こちらのほうがやや簡潔に記述できます。
文字列の削除
部分的に文字列を削除したい場合は、replace関数の置き換え対象文字列に空文字を渡します。
#include <iostream>
#include <string>
int main()
{
std::string str = "ABCDEFG";
//2文字目から3文字を削除
str.replace(2, 3, "");
std::cout << str << std::endl;
//ABFG
std::cin.get();
}
他にerase
関数を使用して文字を削除する方法もあります。
#include <iostream>
#include <string>
int main()
{
using namespace std;
string str = "ABCDEFG";
//2文字目以降を削除
str.erase(2);
cout << str << endl;
//AB
str = "ABCDEFG";
//2文字目から3文字を削除
str.erase(2, 3);
cout << str << endl;
//ABFG
str = "ABCDEFG";
//2文字目から5文字目直前までを削除
str.erase(str.begin() + 2, str.begin() + 5);
cout << str << endl;
//ABFG
cin.get();
}
全ての文字列を削除したい場合はerase関数を引数なしで実行するか、clear
関数を使用します。
#include <iostream>
#include <string>
int main()
{
std::string str1 = "ABC";
std::string str2 = "DEF";
str1.erase();
str2.clear();
//どちらも空
std::cout << str1 << std::endl;
std::cout << str2 << std::endl;
std::cin.get();
}
末尾の一文字を削除
pop_back
関数は、文字列の末尾の一文字を削除します。
この関数はC++11以降で使用可能です。
#include <iostream>
#include <string>
int main()
{
std::string s = "abc";
while (!s.empty()) {
std::cout << s.back() << " ";
s.pop_back();
}
std::cin.get();
}
c b a
戻り値はありません。
文字列の検索
文字列の検索にはいくつかの関数が存在します。
#include <iostream>
#include <string>
int main()
{
using namespace std;
string str = "ABCDEFABCDEF";
//CDが初めて出現する位置を検索
cout << str.find("CD") << endl;
//2
//先頭から3文字目以降でCDが出現する位置を検索
cout << str.find("CD", 3) << endl;
//8
//最後から数えて初めて出現する位置を検索
cout << str.rfind("CD") << endl;
//8
//先頭から3文字目以前でCDが出現する位置を検索
cout << str.rfind("CD", 3) << endl;
//2
//CまたはDが最初に出現する位置を検索
cout << str.find_first_of("CD") << endl;
//2
//CまたはDが最後に出現する位置を検索
cout << str.find_last_of("CD") << endl;
//9
//CまたはD以外が最初に出現する位置を検索
cout << str.find_first_not_of("CD") << endl;
//0
//CまたはD以外が最後に出現する位置を検索
cout << str.find_last_not_of("CD") << endl;
//11
cin.get();
}
通常の検索にはfind
関数を使用します。
先頭を0文字目として、最初に見つかった位置を返します。
find
関数の第二引数は検索開始位置を指定できます。
検索開始位置より前に該当文字列があっても無視されます。
(このような基準点からの相対位置の指定をオフセットといいます)
rfind
関数は、文字列の末尾から検索します。
第二引数は検索開始位置ですが、検索開始位置は文字列の先頭から数えることに注意してください。
第二引数が「3」の場合は、先頭から0~3文字目が検索対象です。
find_first_of
関数は、文字列ではなく文字を検索します。
引数に指定した文字列をバラバラの文字と考え、いずれかの文字が最初に出現する位置を返します。
find_last_of
関数は末尾から数える以外はfind_first_of
関数と同じです。
find_first_not_of
関数は、引数に指定した文字以外が最初に出現する位置を返します。
find_last_not_of
関数は末尾から数える以外はfind_first_not_of
関数同じです。
これらの関数は全て第二引数にオフセットを指定することができます
使用方法はfind
関数と同じなのでサンプルコードでは省略しています。
また、これらの関数で得られるのはすべて文字列を先頭から数えた位置です。
rfind
関数で後方から検索したら後ろから数えた位置が返ってくるとか、そういうことはないので注意しましょう。
戻り値
これらの関数の戻り値はstd::string::size_type
というデータ型です。
これは内部では符号なし整数です。
これらの検索関数は見つからなかったときにstd::string::npos
という値を返します。
これは-1
と定義されていますが、符号なし整数で-1というのは最大値を意味します。
(全てのビットが1であるため)
下手に数値を使用するのではなく、検索の成否の判定はstd::string::npos
と比較するのが確実です。
#include <iostream>
#include <string>
int main()
{
using namespace std;
string str = "ABCDEFABCDEF";
string::size_type index = str.find("XXX");
if (index == string::npos)
cout << "文字列XXXは含まれない" << endl;
else
cout << "文字列XXXの位置: " << index << endl;
cin.get();
}
先頭文字列/末尾文字列の判定
文字列が指定の文字列から開始するかを判定するにはstarts_with
関数を使用します。
指定の文字列で終了するかを判定するにはends_with
関数を使用します。
これらはC++20から使用可能です。
std::string s = "This is a pen.";
bool start1 = s.starts_with("T"); //true
bool start2 = s.starts_with("This"); //true
bool start3 = s.starts_with("That"); //false
bool end1 = s.ends_with("."); //true
bool end2 = s.ends_with("pen."); //true
bool end3 = s.ends_with("pen"); //false
数値との変換
文字列から数値への変換
文字列から数値への変換は「std::stox」系の関数を使用します。
これはメンバ関数ではなくstd
名前空間に属する関数です。
「stox」という名前の関数があるわけではなく、「x」の箇所には対象となるデータ型の省略形が入ります。
(string to xの略)
たとえばint型に変換するにはstd::stoi
関数を使用します。
この関数の第一引数はstd::string
型ですが、文字列リテラルやC言語形式の文字列を渡すと暗黙的な型変換が行われます。
#include <string>
int main()
{
int n;
std::size_t idx;
//基本的な変換
n = std::stoi("123");
//123
//変換できない文字が出現した位置をidxに格納
n = std::stoi("123abc", &idx);
//n=123
//idx=3
//文字列を10進数とみなして変換
n = std::stoi("7b", nullptr, 10);
//7
//文字列を16進数とみなして変換
n = std::stoi("7b", nullptr, 16);
//123
}
第二引数、第三引数は省略可能です。
数値への変換は、先頭から数値として変換できない文字が現れるまでの範囲の文字列が使用されます。
第二引数には変換できない文字が出現した位置を格納するstd::size_t
型変数のアドレスを指定します。
必要ない場合はnullptr
を指定します。
第三引数は文字列の基数を2~36の整数で指定します。
10進数なら「10」、16進数なら「16」です。
初期値は10進数です。
「0」を指定すると文字列のプリフィックスに従って変換を試みます。
int n;
n = std::stoi("012", nullptr, 10);
//12
//先頭が"0"は8進数
n = std::stoi("012", nullptr, 0);
//10
//先頭が"0x"または"0X"は16進数
n = std::stoi("0x12", nullptr, 0);
//18
以下は「stox」系の関数の一覧です。
すべてstd名前空間に属します。
stoi
→int型stol
→long型stoll
→long long型stoul
→unsigned long型stoull
→unsigned long long型stof
→float型stod
→double型stold
→long double型
使い方は同じですが、小数型(stof
、stod
、stold
)の関数には第三引数の基数の指定はありません。
数値型から文字列への変換
数値型の値を文字列に変換するにはstd::to_string
関数を使用します。
std::string s;
s = std::to_string(123); //"123"
s = std::to_string(-123); //"-123"
s = std::to_string(1.23); //"1.230000"
std::to_string
はひととおりの数値型のオーバーロードがあるので、整数型でも小数型でも文字列に変換可能です。
ただし小数型を文字列に変換した場合は末尾にいくつかの0が付くことがあり、これが不要な場合は文字列の桁を削る必要があります。
C言語形式文字列に変換
stringクラスは非常に便利ですが、C言語のライブラリやOSのAPIなどはstringクラスによる文字列を受け付けないため、C言語形式の文字列(終端がNULL文字である文字列)に変換する必要があります。
stringクラス文字列をC言語形式文字列に変換するにはc_str
関数を使用します。
#include <iostream>
#include <string>
int main()
{
std::string str = "ABCDE";
const char *cstr = str.c_str();
std::cout << cstr << std::endl;
//printfができる
printf("%s\n", cstr);
std::cin.get();
}
c_str
関数は、string文字列の末尾にNULL文字(\0
)を付加した文字列へのポインタを返します。
(const char*
型)
通常はこれで問題ありませんが、変換後に元のstring文字列を操作した場合、変換後のデータも改変されます。
文字列の追加などを行った場合、終端のNULL文字が消えてしまい、文字列として正しくない形式になってしまいます。
そのため、c_str
関数は必要になる直前で呼び出し、変換文字列は再利用しないほうがいいでしょう。
(問題がないことが明確な場合は除く)
#include <iostream>
#include <string>
int main()
{
std::string str = "ABCDE";
const char *cstr = str.c_str();
str += "FGHIJ";
//予期せぬエラーが起こる可能性がある
std::printf("%s\n", cstr);
std::cin.get();
}
copy関数
元のstring文字列とは独立してC言語形式文字列を作りたい場合はcopy
関数を使用します。
この関数は元の文字列のコピーを作るので、元の文字列が変更されても変換後の文字列に影響はありません。
- size_type copy(
value_type* ptr,
size_type count,
size_type offset = 0) const; - ソース文字列のoffset文字目から、文字列配列ptrにcount文字分をコピーする。
第一引数ptr
はコピー先のchar型配列を指定します。
第二引数count
はコピーする文字列のサイズを指定します。
第三引数offset
はコピー元文字列のコピー開始位置を指定します。
第三引数は省略可能で、その場合は先頭からコピーを開始します。
戻り値はコピーされた文字数です。
value_type
やsize_type
という見慣れない型が登場しますが、ここではvalue_type
はchar*
型、size_type
型はsize_t
型と同義と考えて問題ありません。
Visual Studioの場合、copy
関数は非推奨となっていて使用できない可能性があるため、代わりに_Copy_s
関数を使用します
- size_type _Copy_s(
value_type* dest,
size_type dest_size,
size_type count,
size_type offset = 0) const; - ソース文字列のoffset文字目から、サイズdest_sizeの文字列配列destにcount文字分をコピーする。
第一引数dest
はコピー先のchar型配列を指定します。
第二引数dest_size
はコピー先の配列のサイズを指定します。
第三引数count
はコピーする文字列のサイズを指定します。
第四引数offset
はコピー元文字列のコピー開始位置を指定します。
第四引数は省略可能で、その場合は先頭からコピーを開始します。
戻り値はコピーされた文字数です。
#include <iostream>
#include <string>
#define BUFFER 32
int main()
{
std::string str = "ABCDE";
char cstr[BUFFER];
size_t len = str.length();
str.copy(cstr, BUFFER, 0);
//Visual Studioでエラーが出る場合は以下を使用
//str._Copy_s(cstr, BUFFER, len, 0);
cstr[len] = '\0';
str += "FGHIJ";
//元のstring文字列に影響されない
printf("%s\n", cstr);
std::cin.get();
}
これらの関数は元の文字列のコピーを作成しますが、末尾にNULL文字は付加されません。
そのためC言語形式の文字列として使用する場合は手動で末尾にNULL文字を挿入する必要があります。
コピーした文字数をそのまま配列の添字に指定すればそこが文字列の終端ですから、そこにNULL文字に書き換えます。
コピー先の配列のサイズに余裕がないとバグの原因になるので気を付けてください。
C言語形式配列からstringクラス文字列に
stringクラスの初期化や各関数のほとんどはC言語形式の文字列をそのまま受け取れるので、特に意識して変換する必要はありません。