[C++] std::string のメモまとめ


wstring と wstringへの変換

std::basic_string は std::string クラス
std::basic_string は std::wstring クラス

string クラスは、 char文字列(シングルバイト文字列、マルチバイト文字列) を扱えるクラス(UTF8)
wstring クラスは wchar_t文字列(ワイド文字列) を扱えるクラス(UTF16, UTF32)

まぁ cocos2d-x の開発は基本 UTF8 なので基本的に wstring クラスの出番はない。

文字列の結合(append, operator+=)

基本的には += 演算子で結合すると楽。
メソッドを使用する場合は append メソッドで結合。
append を使うことで文字列の一部を結合することや、
同じ種類の連続した文字を結合することが可能。

std::string text;

text += "123";
puts(text.c_str()); // 123

text += '#';
puts(text.c_str()); // 123#

text += 97;
puts(text.c_str()); // 123#a

text += std::string("XXX");
puts(text.c_str()); // 123#aXXX
std::string text;

text.append("123");
puts(text.c_str()); // 123

text.append(3, '#');
puts(text.c_str()); // 123###

text.append(1, 97);
puts(text.c_str()); // 123###a

text.append(std::string("XXX"));
puts(text.c_str()); // 123###aXXX

std::string sample = "0123456789";
text.append(sample, 7, 2);
puts(text.c_str()); // 123###aXXX78

std::string::const_iterator start = sample.begin();
start += 3;
std::string::const_iterator end = sample.end();
end -= 3;
text.append(start, end);
puts(text.c_str()); // 123###aXXX783456

assign と operator=

文字列の設定をする。
assignの引数はappendとたぶん全部同じ。

std::string text("###");

text = "ABCDE";
puts(text.c_str()); //ABCDE

text.assign(text, 2, 3);
puts(text.c_str()); // CDE

text = "xxx";
puts(text.c_str()); // xxx

at と operator[]

該当箇所の値を取得

std::string text = "ABCDE";

char a = text.at(3);
char b = text[3];

puts(a == b ? "true" : ""); // true

compare と 比較演算子

compareは名前通りの比較メソッド。strcmpと同じ。
compareを使わなくても ==, !=, <, >, <=, >= で同様の比較は可能だが、
文字の一部を比較するといった場合は compare を使う。

printf("%d \n", std::string("aaa").compare("aaa")); // 0
printf("%d \n", std::string("aaa").compare("aa")); // 1
printf("%d \n", std::string("aa").compare("aaa")); // -1

std::string text = "0123456789";

// 違う文字の比較
printf("%d \n", text.compare("9")); // -9
printf("%d \n", std::string("9").compare(text)); // 9
printf("%d \n", '0' - '9'); // -9

// textの一部を比較
printf("%d \n", text.compare(0, 3, "012")); // 0

// 2つの文字列の先頭3文字の比較
printf("%d \n", text.compare(0, 3, "012###", 0, 3)); // 0

// それぞれの末尾3文字の比較
printf("%d \n", text.compare(text.size() - 3, 3,
 "###789", strlen("###789") - 3, 3)); // 0
std::string text = "012";
printf("%d \n", text.compare(0, 10, "012")); // 0
printf("%d \n", text.compare(0, 10, "012", 0, 10)); // 0

c_str と data

c_str は std::string の内部バッファの先頭ポインタを返す。
その際に、文字の末尾を NULL文字 にして返す。

c_str の返り値のポインタは std::string の保持するデータ領域が再確保されない限りは有効。

std::string text = "123";

const char *cstr = text.c_str();
puts(cstr); // 123

text += "456";

puts(cstr == text.c_str() ? "true" : ""); // true
puts(cstr); // 123456

text.reserve(10000);
puts(cstr == text.c_str() ? "" : "false"); // false

data は末尾にNULL文字を付けずに返すらしいんだけど、ホントか??

//
// data と c_str の違いが全くわからない…
//
std::string text("012", 100);
text[3] = '3';
text[4] = '\0';

puts(text.data()); // 0123
puts(text.c_str()); // 0123
puts(text.data()); // 0123

puts(text.data() == text.c_str() ? "true" : ""); // true

まぁ、dataは末尾がNULL文字だと保証されないようなのでその点に注意。
上のコードのような真似をすると c_str も末尾をNULL文字にしてくれるとは限らないようだけど…

size と length と empty

size() は 文字数を返す。
length は size のエイリアス。
empty() は (size() == 0) と等価

std::string text = "abc";
puts(text.empty() ? "" : "false"); // false

text.resize(0);
puts(text.empty() ? "true" : ""); // true

printf("%ld \n", text.size()); // 0

text[0] = 'X';
text[1] = '\0';
printf("%ld \n", text.size()); // 0

puts(text.c_str()); // X

resize と reserve

リサイズは文字列のサイズを変更(して増えた部分は指定した文字で埋める)
リザーブは文字列のキャパシティを上げる。

resize(0) と erase() は等価

std::string text("ABCDE");
puts(text.c_str()); // ABCDE
printf("%ld \n", text.size()); // 5

/*
 reserve
 capacityで取得できる値はreserveに指定した値以上になる。
 どのような値になるかは環境によって異なる??
 */
printf("%ld \n", text.capacity()); // 22

text.reserve(100);
printf("%ld \n", text.capacity()); // 111

text.reserve(112);
printf("%ld \n", text.capacity()); // 127

printf("%ld \n", text.size()); // 5

/*
 resize
 */
text.resize(3);
puts(text.c_str()); // ABC
printf("%ld \n", text.size()); // 3

// resizeでreserveで確保済みの領域を超えない場合は
// 確保済みの領域のサイズには影響なし
printf("%ld \n", text.capacity()); // 127

text.resize(0);
puts(text.c_str()); // (空文字)
printf("%ld \n", text.size()); // 0

// resizeの第2引数は
// resizeで現在のsizeを超える値を指定した場合に
// 元サイズを超えた部分を埋める値として使われる。
text.resize(3, 'x');
puts(text.c_str()); // xxx
printf("%ld \n", text.size()); // 3

// resizeで現在のsizeよりも大きい値を指定すると
// 増えた文字の部分は 0 で埋められる
text.resize(10);
puts(text.c_str()); // xxx
printf("%ld \n", text.size()); // 10
printf(text[5] == 0 ? "zero" : ""); // zero

コンストラクタでリザーブする

/*
 デフォルトコンストラクタで作成しても
 ある程度capacityは確保されてる。
 */
std::string a;
printf("%ld \n", a.capacity()); // 22

/*
 string(文字列, 長さ) の第2引数でリザーブできる。
 ※第1引数の文字数の方が大きい場合は関係ないけど…
 リザーブ領域はゼロクリアされたりしない。

 また、第2引数に0を指定してもある程度capacityは確保されてる。
 */

/*
 リザーブ領域を初期化したい場合は
 string(サイズ, char 埋める文字) のコンストラクタを使う
 */
std::string d(100, 'x');
d.resize(0);
printf("%ld \n", b.capacity()); // 111
printf("%c \n", d[50]); // x

///
/// ★ 2016/5/11 いまさら気づいたけどこれ酷いこと書いてあんね・・・ごめんなさい。
/// ★ 空文字を超えて文字列を読み取るというひどいというかやばいコード
///
/* ★★★ ↓ これは最強レベルに危険 ↓ ★★★ */
std::string b("", 100);
b.resize(0);
printf("%ld \n", b.capacity()); // 111
*/

リザーブしない場合の容量の増え方

倍々に増えていく。

std::string a;
size_t capacity = 0;
for (int i = 0; i < 1000000; i++)
{
	a += "x";
	if (capacity != a.capacity()) {
		capacity = a.capacity();
		printf("%ld \n", capacity);
	}
}

結果の一例

22
47
95
191
383
767
1535
3071
6143
12287
24575
49151
98303
196607
393215
786431
1572863

erase

文字列の一部分(または全て)を消す

#define TEXT std::string("0123456789")

// 引数なしの erase() は resize(0) と等価 = 空文字にする
printf("%ld \n", TEXT.erase().size()); // 0

// 引数1つの erase(start) は start 以降を消す
puts(TEXT.erase(5).c_str()); // 01234

// 引数2つの erase(start, 文字数) は startから文字数だけ消す
puts(TEXT.erase(3, 5).c_str()); // 01289

// イテレータ使う場合
std::string text(TEXT);
std::string::const_iterator begin = text.begin();
begin += 3;
std::string::const_iterator end = text.end();
end -= 4;
text.erase(begin, end);
puts(text.c_str()); // 0126789

erase vs resize(0) vs assign(“”)

#define TEXT std::string("0123456789")

CFAbsoluteTime totalErase = 0;
CFAbsoluteTime totalResize0 = 0;

CFAbsoluteTime t1, t2;
for (int i = 0; i < 5000; i++) {
	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) TEXT.erase();
	t2 = CFAbsoluteTimeGetCurrent();
	totalErase += t2 - t1;

	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) TEXT.resize(0);
	t2 = CFAbsoluteTimeGetCurrent();
	totalResize0 += t2 - t1;
}

for (int i = 0; i < 5000; i++) {
	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) TEXT.resize(0);
	t2 = CFAbsoluteTimeGetCurrent();
	totalResize0 += t2 - t1;

	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) TEXT.erase();
	t2 = CFAbsoluteTimeGetCurrent();
	totalErase += t2 - t1;
}

printf("erase = %.3f, resize(0) = %.3f, ratio = %.3f",
       totalErase,
       totalResize0,
       totalErase / totalResize0);

結果の一例

erase = 3.470, resize(0) = 2.952, ratio = 1.176

resize(0)の方がわずかに速い。eraseがゼロクリアしているとかそういうことはないので処理結果はたぶん等価なはずだけど、中身の処理内容でちょっと速度が異なるらしい。

ちなみに assing(“”) は erase()よりも遅い。そういうことで文字列を消去したい場合は resize(0) が最も高速っぽい。

substr 文字列の一部を使用して新しい文字列を生成

substr は元の文字列には影響しない。

std::string text = "0123456789";
puts(text.substr(3).c_str()); // 3456789
puts(text.substr(3, 5).c_str()); // 34567

コンストラクタで文字列の一部を使う

std::string sample = "0123456789";

// 他の文字列の一部を切り出す
std::string a(sample, 3, 4);
puts(a.c_str()); // 3456

// イテレータを使用する場合
std::string::const_iterator start = sample.begin();
start += 2;
std::string::const_iterator end = sample.end();
end -= 2;
std::string b(start, end);
puts(b.c_str()); // 234567

// リバースイテレータを使用する場合
std::string::const_reverse_iterator startReverse = sample.rbegin();
startReverse += 2;
std::string::const_reverse_iterator endReverse = sample.rend();
endReverse -= 2;
std::string c(startReverse, endReverse);
puts(c.c_str()); // 765432

swap

文字列を入れ替えるだけなのだけど、入れ替え後のリザーブ領域の場所が興味深い。

std::string a = "ABC";
std::string b = "012345678901234567890123456789";

const char *cstrA = a.c_str();
const char *cstrB = b.c_str();

printf("capacity: a = %ld, b = %ld \n",
       a.capacity(),
       b.capacity()); // capacity: a = 22, b = 31

a.swap(b);
printf("capacity: a = %ld, b = %ld \n",
       a.capacity(),
       b.capacity()); // capacity: a = 31, b = 22

puts(a.c_str()); // 012345678901234567890123456789
puts(cstrB);     // 012345678901234567890123456789
puts(a.c_str() == cstrB ? "true" : "false"); // true

puts(b.c_str()); // ABC
puts(cstrA); // (何も出力されない)
puts(b.c_str() == cstrA ? "true" : "false"); // false
std::string a("ABC", 1000);
std::string b("012345678901234567890123456789", 10000);

const char *cstrA = a.c_str();
const char *cstrB = b.c_str();

printf("capacity: a = %ld, b = %ld \n",
       a.capacity(),
       b.capacity()); // capacity: a = 1007, b = 10015

a.swap(b);
printf("capacity: a = %ld, b = %ld \n",
       a.capacity(),
       b.capacity()); // capacity: a = 10015, b = 1007

puts(a.c_str() == cstrB ? "true" : "false"); // true
puts(b.c_str() == cstrA ? "true" : "false"); // true

なんか納得イカないな…リザーブ領域交換してるだけじゃないのかな…

コンストラクタに const char * 渡すとコピーが発生する

まぁ可変長文字列なので違和感ない結果だけども。。

const char *cstr = "0123456789";
std::string text(cstr);

puts(cstr == text.c_str() ? "true" : "false"); // false

insert で文字列や文字を挿入

以下のコードでは挿入場所はインデックス値を指定しているけど、イテレータも使える。

std::string text("3");

// 文字列を挿入
puts(text.insert(0, "012").c_str()); // 0123
puts(text.insert(1, "#").c_str()); // 0#123

// 文字列の一部を挿入
puts(text.insert(2, "@@@@@", 3).c_str()); // 0#@@@123
puts(text.insert(2, "ABCDE", 1, 3).c_str()); // 0#BCD@@@123

// 文字を挿入
puts(text.insert(5, 3, '+').c_str()); // 0#BCD+++@@@123

replace で文字列置換

以下のコードではイテレータは使ってないけど、置換箇所の指定にイテレータを使うことが可能。

std::string text("0123456789");

puts(text.replace(2, 3, "#####").c_str()); // 01#####56789
puts(text.replace(2, 5, "ABCDE", 3).c_str());// 01ABC56789
puts(text.replace(6, 3, "ABCDE", 3, 2).c_str()); // 01ABC5DE9
puts(text.replace(2, 5, 1, '*').c_str());// 01*E9

max_size

使うどころがわからないけど std::string クラスが扱える文字数の最大値を返す関数。

std::string text;
size_t maxSize = text.max_size();
printf("%lu", maxSize); // 18446744073709551614

find と rfind

findもrfindも文字列検索を実行するけど、rfindは検索開始位置から後ろ方向に探索する。

std::string text("0123456789");

// 見つからない場合は std::string::npos を返す
puts(text.find("#") == std::string::npos ? "true" : ""); // true

// シンプルな検索
printf("%lu \n", text.find("345")); // 3

// 検索開始位置を指定
printf("%lu \n", text.find("345", 3)); // 3
printf("%lu \n", text.find("345", 4)); // 18446744073709551615

// 検索開始位置と、検索文字列のうち検索にしようする文字の長さを指定
// ちょっと分かりにくい。
printf("%lu \n", text.find("345678###", 3, 6)); // 3
printf("%lu \n", text.find("345678###", 3, 7)); // 18446744073709551615

// char で検索 - 第2引数は検索開始位置
printf("%lu \n", text.find('3')); // 3
printf("%lu \n", text.find('3', 3)); // 3
printf("%lu \n", text.find('3', 4)); // 18446744073709551615

※18446744073709551615 は string::npos

std::string text("0123456789");

// 見つからない場合は std::string::npos を返す
puts(text.rfind("#") == std::string::npos ? "true" : ""); // true

// シンプルな検索 - 最後の文字から検索
printf("%lu \n", text.rfind("345")); // 3

// 検索開始位置を指定 - その位置から後ろに探索していく
printf("%lu \n", text.rfind("345", 3)); // 3
printf("%lu \n", text.rfind("345", 2)); // 18446744073709551615

// 検索開始位置と、検索文字列のうち検索にしようする文字の長さを指定
// ちょっと分かりにくい。
printf("%lu \n", text.rfind("345678###", 3, 6)); // 3
printf("%lu \n", text.rfind("345678###", 2, 6)); // 18446744073709551615
printf("%lu \n", text.rfind("345678###", 3, 7)); // 18446744073709551615

// char で検索 - 第2引数は検索開始位置
printf("%lu \n", text.rfind('3')); // 3
printf("%lu \n", text.rfind('3', 3)); // 3
printf("%lu \n", text.rfind('3', 2)); // 18446744073709551615

find_first_of, find_last_of, find_first_not_of, find_first_last_of

文字列じゃなくて文字を検索するメンバ関数。
find_first_of と find_last_of の関係は find と rfind の関係と同じ。

/*
 find_first_of
 */
std::string text("0123456789");

// "963" の内最初に出現する文字の位置
printf("%lu \n", text.find_first_of("963")); // 3

// 第2引数で検索開始位置を指定
printf("%lu \n", text.find_first_of("963", 4)); // 6

// 第3引数で 第1引数の文字で何文字まで利用するかを指定
printf("%lu \n", text.find_first_of("963", 4, 1)); // 9
printf("%lu \n", text.find_first_of("963", 4, 2)); // 6

// 文字を検索することもできる - 1文字検索する場合は find より速いのかな?
printf("%lu \n", text.find_first_of('3', 3)); // 3
printf("%lu \n", text.find_first_of('3', 4)); // 18446744073709551615

/*
 find_last_of
 */
printf("%lu \n", text.find_last_of("963")); // 9
printf("%lu \n", text.find_last_of("963", 4)); // 3

/*
 find_first_not_of
 */
printf("%lu \n", text.find_first_not_of("012789")); // 3
printf("%lu \n", text.find_first_not_of("012789", 4)); // 4

/*
 find_last_not_of
 */
printf("%lu \n", text.find_last_not_of("012789")); // 6
printf("%lu \n", text.find_last_not_of("012789", 4)); // 4

1文字を検索するときは find よりも find_first_of の方が速い

std::string text("0123456789");

CFAbsoluteTime totalFind = 0;
CFAbsoluteTime totalFindFirstOf = 0;

CFAbsoluteTime t1, t2;
for (int i = 0; i < 5000; i++) {
	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) text.find("5");
	t2 = CFAbsoluteTimeGetCurrent();
	totalFind += t2 - t1;

	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) text.find_first_of('5');
	t2 = CFAbsoluteTimeGetCurrent();
	totalFindFirstOf += t2 - t1;
}

for (int i = 0; i < 5000; i++) {
	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) text.find_first_of('5');
	t2 = CFAbsoluteTimeGetCurrent();
	totalFindFirstOf += t2 - t1;

	t1 = CFAbsoluteTimeGetCurrent();
	for (int k = 0; k < 10000; k++) text.find("5");
	t2 = CFAbsoluteTimeGetCurrent();
	totalFind += t2 - t1;
}

printf("find = %.3f, find_first_of = %.3f, ratio = %.3f",
       totalFind,
       totalFindFirstOf,
       totalFind / totalFindFirstOf);

結果の一例

find = 3.361, find_first_of = 2.025, ratio = 1.659
    • 匿名
    • 2014年 5月22日

    replace の引数が何をさしているのかわからない

    • 匿名
    • 2015年 4月4日

    findとfind_first_ofの速度比較においてなんでfindは文字列指定なのにfind_first_ofだけ文字指定なのでしょう?
    どちらもオーバーロード保障されているのでは?

    • Fernweh
    • 2015年 4月12日

    > 匿名 2015年 4月4日
    ご指摘ありがとうございます。
    確かにそんな気がするので、確認しておきます。

  1. 2015年 4月21日
*


Advertisement