33. 文字列クラス
文字列クラス String の基本的な使い方を学びます。
33.1 String
- Siv3D では String型を使って文字列を表現します
- Stringは、UTF-32 のコードポイントを表現する- char32型(文字)の配列です
- UTF-32 の文字や文字列リテラルには、U'あ',U"Hello"のようにUプレフィックスを付けます
- Stringは内部で- std::u32stringを使って文字列を管理します
- 格納されている文字列データについて、次のことが保証されています
- 文字列データがメモリ上で連続していること
- 文字列の最終要素の直後にヌル文字 U'\0'があること
 
- C++ 標準ライブラリの文字列クラスよりも多くのメンバ関数を持ち、様々な便利な機能を提供します
String s1 = U"Siv3D";
String s2 = U"こんにちは、Siv3D!";
33.2 文字列の作成
- Stringは次のような方法で作成します- 
- 空の文字列を作成
- 文字列リテラルから文字列を作成
- 個数 × 文字で文字列を作成
 
# include <Siv3D.hpp>
void Main()
{
	{
		// 空の文字列を作成する
		String s;
		Print << s;
	}
	{
		// 文字列リテラルから文字列を作成する
		String s = U"Siv3D";
		Print << s;
	}
	{
		// 個数 × 文字で文字列を作成する
		String s(5, U'A');
		Print << s;
	}
	while (System::Update())
	{
	}
}
33.3 文字列の長さ
- .size()は文字列の長さ(要素数)を- size_t型で返します
- 文字列の長さは、char32型で表現される UTF-32 のコードポイントの数です
- アルファベットやひらがな、漢字など、大半の文字は 1 文字が 1 コードポイントです
- 一部の絵文字などの特殊な文字では、見た目が 1 文字でも複数のコードポイントから構成されていることがあります
# include <Siv3D.hpp>
void Main()
{
	{
		String s = U"Siv3D";
		Print << s.size();
	}
	{
		String s = U"こんにちは";
		Print << s.size();
	}
	{
		String s = U"🐈";
		Print << s.size();
	}
	{
		// 複数のコードポイントから構成されている絵文字もある
		String s = U"👩🎤";
		Print << s.size();
	}
	while (System::Update())
	{
	}
}
33.4 空であるかを調べる(1)
- .isEmpty()は文字列が空であるか(要素数が 0 であるか)を- bool型で返します
# include <Siv3D.hpp>
void Main()
{
	String s1 = U"Siv3D";
	Print << s1.isEmpty();
	String s2;
	Print << s2.isEmpty();
	while (System::Update())
	{
	}
}
33.5 空であるかを調べる(2)
- if (s)を使って文字列がからであるかを調べます
- 文字列が空である場合、falseと評価されます
# include <Siv3D.hpp>
void Main()
{
	String s1 = U"Siv3D";
   
	if (s1)
	{
		Print << U"s1 is not empty";
	}
	String s2;
	if (not s2)
	{
		Print << U"s2 is empty";
	}
	while (System::Update())
	{
	}
}
出力s1 is not empty
s2 is empty
 
33.6 末尾への要素追加
- s << ch;で文字列- sの末尾に要素- chを追加します
- .push_back(ch)を短く書けるようにしたものです
- 文字列の末尾への追加は、それ以外の場所(先頭や途中)への追加に比べて最も効率的です
# include <Siv3D.hpp>
void Main()
{
	String s;
	s << U'S';
	s << U'i';
	s << U'v';
	Print << s;
	s << U'3' << U'D';
	Print << s;
	while (System::Update())
	{
	}
}
33.7 末尾の要素の削除
- .pop_back()で文字列の末尾の要素を削除します
- 要素数が 0 のときに呼び出してはいけません
- 文字列の末尾の要素の削除は、それ以外の場所(先頭や途中)の削除に比べて最も効率的です
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	Print << s;
	s.pop_back();
	Print << s;
	s.pop_back();
	Print << s;
	while (s)
	{
		s.pop_back();
	}
	Print << s.isEmpty();
	while (System::Update())
	{
	}
}
33.8 すべての要素の削除
- .clear()で文字列のすべての要素を削除し、空の文字列にします
- 要素数が 0 のときに呼び出しても問題ありません(何もしません)
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	Print << s;
	s.clear();
	Print << s.isEmpty();
	while (System::Update())
	{
	}
}
33.9 要素数の変更
- .resize(n)で文字列の要素数を- nに変更します
- 要素数が増える場合、新しい要素はヌル文字 U'\0' で初期化されるので、別の文字で上書きする必要があります
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	Print << s;
	s.resize(3);
	Print << s;
	s.resize(5);
	s[3] = U'!';
	s[4] = U'?';
	Print << s;
	while (System::Update())
	{
	}
}
33.10 範囲 for 文による配列走査(const 参照)
- 範囲 for 文を使って配列の要素を走査します
- 各要素へのアクセスは、通常は const 参照で行います
- 範囲 for 文の中で、対象の文字列のサイズを変更する操作は行わないでください
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	for (const auto& ch : s)
	{
		Print << ch;
	}
	while (System::Update())
	{
	}
}
33.11 範囲 for 文による配列走査(参照)
- 範囲 for 文を使って配列の要素を走査します
- ループ内で要素を変更する場合、const 参照の代わりに参照を使って要素にアクセスします
- 範囲 for 文の中で、対象の文字列のサイズを変更する操作は行わないでください
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	for (auto& ch : s)
	{
		++ch;
	}
	Print << s;
	while (System::Update())
	{
	}
}
33.12 指定したインデックスの要素へのアクセス
- [i]で文字列の- i番目の要素にアクセスします- 
- iは 0 から数えます。有効なインデックスは 0 から- size() - 1までです
 
- 範囲外にアクセスしてはいけません
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	Print << s[0];
	Print << s[4];
	s[3] = U'4';
	Print << s;
	while (System::Update())
	{
	}
}
33.13 先頭・末尾の要素へのアクセス
- .front()は先頭の要素への参照を返します
- .back()は末尾の要素への参照を返します
- いずれも要素数が 0 のときに呼び出してはいけません
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	Print << s.front();
	Print << s.back();
	s.front() = U's';
	s.back() = U'd';
	Print << s;
	while (System::Update())
	{
	}
}
33.14 2 つの文字列を結合した文字列の作成
- s1 + s2で左右の文字列- s1,- s2を結合した新しい文字列を作成できます
- s1や- s2は変更されません
# include <Siv3D.hpp>
void Main()
{
	String s1 = U"Hello, ";
	String s2 = U"Siv3D!";
	Print << (s1 + s2);
	Print << (s1 + s2 + U"!!!");
	while (System::Update())
	{
	}
}
出力Hello, Siv3D!
Hello, Siv3D!!!!
 
33.15 末尾への文字列追加
# include <Siv3D.hpp>
void Main()
{
	String s = U"Hello, ";
	s += U"Siv3D!";
	Print << s;
	s += U"!!!";
	Print << s;
	while (System::Update())
	{
	}
}
出力Hello, Siv3D!
Hello, Siv3D!!!!
 
33.16 先頭・終端位置のイテレータ取得
- .begin()は先頭位置のイテレータを返します
- .end()は終端位置のイテレータを返します
# include <Siv3D.hpp>
void Main()
{
	String s = U"Siv3D";
	auto it = s.begin();
	Print << *it;
	++it;
	Print << *it;
	
	while (System::Update())
	{
	}
}
33.17 その他の挿入・削除操作
- .push_front(値)で、先頭に要素を追加します
- .pop_front()で、先頭の要素を削除します
- .insert(イテレータ, 値)で、指定したイテレータの位置に要素を挿入します
- .erase(イテレータ)で、指定したイテレータの位置の要素を削除します
- .erase(イテレータ1, イテレータ2)で、指定した範囲の要素を削除します
- 先頭や途中への要素の挿入・削除は、それ以降の既存要素の移動を伴うため、うしろの要素数に比例したコストがかかります
- 通常は避けるか、小さい文字列でのみ使用するべきです
 
# include <Siv3D.hpp>
void Main()
{
	{
		String s = U"Siv3D";
		
		s.push_front(U'#');
		Print << s;
		s.pop_front();
		Print << s;
	}
	{
		String s = U"Siv3D";
		
		s.insert((s.begin() + 3), U'#');
		Print << s;
	}
	{
		String s = U"Siv3D";
		s.erase(s.begin() + 3);
		Print << s;
		s.erase(s.begin(), (s.begin() + 2));
		Print << s;
	}
	while (System::Update())
	{
	}
}
出力#Siv3D
Siv3D
Siv#3D
SivD
vD
 
33.18 ある文字や文字列を含むかを調べる
- .contains(文字)は、文字列が指定した文字を含むかを- bool型で返します
- .contains(文字列)は、文字列が指定した文字列を含むかを- bool型で返します
# include <Siv3D.hpp>
void Main()
{
	String s = U"Hello, Siv3D!";
	Print << s.contains(U'S');
	Print << s.contains(U'i');
	Print << s.contains(U'4');
	Print << s.contains(U"3D");
	Print << s.contains(U"Hello");
	Print << s.contains(U"Hi");
	while (System::Update())
	{
	}
}
出力true
true
false
true
true
false
 
33.19 ある文字や文字列で始まるか・終わるかを調べる
- .starts_with(文字)は、文字列が指定した文字で始まるかを- bool型で返します
- .starts_with(文字列)は、文字列が指定した文字列で始まるかを- bool型で返します
- .ends_with(文字)は、文字列が指定した文字で終わるかを- bool型で返します
- .ends_with(文字列)は、文字列が指定した文字列で終わるかを- bool型で返します
# include <Siv3D.hpp>
void Main()
{
	String s = U"Hello, Siv3D!";
	Print << s.starts_with(U'H');
	Print << s.starts_with(U'S');
	Print << s.starts_with(U"Hello");
	Print << s.starts_with(U"Hi");
	Print << s.ends_with(U'!');
	Print << s.ends_with(U'D');
	Print << s.ends_with(U"3D!");
	Print << s.ends_with(U"Hi");
	while (System::Update())
	{
	}
}
出力true
false
true
false
true
false
true
false
 
33.20 部分文字列の作成
- .substr(開始位置, 長さ)で、文字列の- 開始位置から- 長さ文字の部分文字列を、新しい- Stringとして作成します- 
- 開始位置は 0 から始まります
- 長さが省略された場合は、- 開始位置文字目から末尾までの部分文字列を作成します
- 長さが実際の文字列の長さより大きい場合は、ちょうど末尾までの部分文字列を作成します
 
# include <Siv3D.hpp>
void Main()
{
	const String s = U"Hello, Siv3D!";
	Print << s.substr(0, 5);
	Print << s.substr(7, 3);
	Print << s.substr(7);
	Print << s.substr(0, 100);
	while (System::Update())
	{
	}
}
出力Hello
Siv
Siv3D!
Hello, Siv3D!
 
- 次のように、時間経過に応じて文字列を表示するプログラムに応用することができます
# include <Siv3D.hpp>
void Main()
{
	const String s = U"Hello, Siv3D!";
	Stopwatch stopwatch{ StartImmediately::Yes };
	while (System::Update())
	{
		ClearPrint();
		const int32 count = (stopwatch.ms() / 300);
		Print << s.substr(0, count);
	}
}
33.21 アルファベットを小文字 / 大文字にする
- .lowercased()は、アルファベットを小文字にした新しい文字列を作成します
- .uppercased()は、アルファベットを大文字にした新しい文字列を作成します
# include <Siv3D.hpp>
void Main()
{
	const String s = U"こんにちは、Siv3D!";
	// 小文字にした新しい文字列を作成する
	Print << s.lowercased();
	// 大文字にした新しい文字列を作成する
	Print << s.uppercased();
	while (System::Update())
	{
	}
}
出力こんにちは、siv3d!
こんにちは、SIV3D!
 
33.22 文字列の逆順
- .reversed()は、文字列を逆順にした新しい文字列を作成します
- .reverse()は、文字列を逆順にします
# include <Siv3D.hpp>
void Main()
{
	const String s1 = U"Hello, Siv3D!";
	// 逆順にした新しい文字列を作成する
	Print << s1.reversed();
	String s2 = U"Hello, Siv3D!";
	// 文字列を逆順にする
	s2.reverse();
	Print << s2;
	while (System::Update())
	{
	}
}
出力!D3viS ,olleH
!D3viS ,olleH
 
33.23 文字列の要素のシャッフル
- .shuffled()は、文字列の要素をシャッフルした新しい文字列を作成します
- .shuffle()は、文字列の要素をシャッフルします
# include <Siv3D.hpp>
void Main()
{
	const String s1 = U"Hello, Siv3D!";
	
	// シャッフルした新しい文字列を作成する
	Print << s1.shuffled();
	String s2 = U"Hello, Siv3D!";
	// 文字列の要素をシャッフルする
	s2.shuffle();
	Print << s2;
	while (System::Update())
	{
	}
}
出力例vel SDH!,loi3
3 lo!vl,SHDie
 
33.24  文字や文字列の置換
- .replaced(from, to)は、文字列の中の- fromを- toに置き換えた新しい文字列を作成します
- .replace(from, to)は、文字列の中の- fromを- toに置き換えます
# include <Siv3D.hpp>
void Main()
{
	String s = U"Hello, Siv3D!";
	
	// Siv3D を C++ に置き換えた新しい文字列を作成する
	Print << s.replaced(U"Siv3D", U"C++");
	
	// ! を ? に置き換える
	s.replace(U'!', U'?');
	Print << s;
	// Hello を Hi に置き換えた新しい文字列を作成する
	Print << s.replaced(U"Hello", U"Hi");
	while (System::Update())
	{
	}
}
出力Hello, C++!
Hello, Siv3D?
Hi, Siv3D?
 
33.25 前後の空白文字の削除
- .trimmed()は、文字列の前後にある空白文字(スペース、タブ、改行など)を削除した新しい文字列を作成します
- .trim()は、文字列の前後にある空白文字を削除します
# include <Siv3D.hpp>
void Main()
{
	String s1 = U" Hello, Siv3D!   ";
	// 前後の空白文字を削除する
	s1.trim();
	Print << s1;
	Print << s1.size();
	const String s2 = U"\n\n Siv3D  \n\n\n";
	// 前後の空白文字を削除した新しい文字列を作成する
	Print << s2.trimmed();
	while (System::Update())
	{
	}
}
33.26 指定した文字による分割
- .split(delimiter)は、文字列を- delimiterで分割した結果を- Array<String>で返します- 
- delimiterは 1 文字の文字列です
- delimiterが連続している場合、空の文字列が生成されます
- delimiterが文字列の先頭や末尾にある場合、空の文字列が生成されます
- delimiterが文字列に含まれていない場合、元の文字列がそのまま返されます
 
# include <Siv3D.hpp>
void Main()
{
	{
		const String s = U"red,green,blue";
		
		const Array<String> values = s.split(U',');
		Print << values;
	}
	{
		const String s = U",,a,";
		const Array<String> values = s.split(U',');
		Print << values;
	}
	{
		const String s = U"1, 2, 3, 4, 5";
		// U',' で区切られた数字の文字列を Array<int32> に変換する例
		const Array<int32> values = s.split(U',').map(Parse<int32>);
		Print << values;
	}
	while (System::Update())
	{
	}
}
出力{red, green, blue}
{, , a, }
{1, 2, 3, 4, 5}
 
33.27 他の文字列型への変換
- Stringを別の形式の文字列型に変換する次のようなメンバ関数があります
| コード | 説明 | 
| .narrow() | std::string(文字コードは環境依存)に変換します | 
| .toUTF8() | std::string(UTF-8)に変換します | 
| .toWstr() | std::wstringに変換します | 
| .toUTF16() | std::u16stringに変換します | 
| .toUTF32() | std::u32stringに変換します | 
環境依存の文字コード
- std::stringの文字コードは環境によって異なります
- 日本語の Windows では Shift_JIS (CP932), macOS や Linux では UTF-8 です
- Siv3D v0.8 以降では std::stringの文字コードは UTF-8 に統一されます
 
# include <Siv3D.hpp>
void Main()
{
	const String s = U"こんにちは、Siv3D!";
	const std::string s1 = s.narrow();
	const std::string s2 = s.toUTF8();
	const std::wstring s3 = s.toWstr();
	const std::u16string s4 = s.toUTF16();
	const std::u32string s5 = s.toUTF32();
	while (System::Update())
	{
	}
}
- const char*を受け取る関数に- Stringの文字列を渡すには、- .narrow()で得られた- std::stringの先頭ポインタを- c_str()で取得します
33.28 他の文字列型からの変換
- 別の形式の文字列型から Stringに変換する次のような関数があります
| コード | 説明 | 
| Unicode::Widen(s) | std::string(文字コードは環境依存)からStringに変換します | 
| Unicode::WidenAscii(s) | std::string(ASCII)からStringに変換します | 
| Unicode::FromWstring(s) | std::wstringからStringに変換します | 
| Unicode::FromUTF8(s) | std::string(UTF-8)からStringに変換します | 
| Unicode::FromUTF16(s) | std::u16stringからStringに変換します | 
| Unicode::FromUTF32(s) | std::u32stringからStringに変換します | 
# include <Siv3D.hpp>
void Main()
{
	const String s1 = Unicode::Widen("こんにちは、Siv3D!");
	const String s2 = Unicode::WidenAscii("Hello, Siv3D!");
	const String s3 = Unicode::FromWstring(L"こんにちは、Siv3D!");
	const String s4 = Unicode::FromUTF8("こんにちは、Siv3D!");
	const String s5 = Unicode::FromUTF16(u"こんにちは、Siv3D!");
	const String s6 = Unicode::FromUTF32(U"こんにちは、Siv3D!");
	while (System::Update())
	{
	}
}
33.29 FilePath
- ファイルパスを指す文字列であることを明示するために、StringのエイリアスとしてFilePathが用意されています
# include <Siv3D.hpp>
void Main()
{
	FilePath path = U"example/windmill.png";
	Print << path;
	while (System::Update())
	{
	}
}