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型

使い方は同じですが、小数型(stofstodstold)の関数には第三引数の基数の指定はありません。

数値型から文字列への変換

数値型の値を文字列に変換するには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_typesize_typeという見慣れない型が登場しますが、ここではvalue_typechar*型、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言語形式の文字列をそのまま受け取れるので、特に意識して変換する必要はありません。