C++編【言語解説】 第4章 文字列

この章の概要

この章の概要です。

C言語の文字列

C言語では、文字列を表現する際には、char型の配列を利用しました(C言語編第25章)。 この方法は C++ でもそのまま使えますが、C++ には、より便利で安全な方法が提供されています。 それが、この章の主題である string というものです。

string の利用

まずは、簡単なサンプルプログラムをご覧下さい。

#include <iostream>
#include <string>

int main()
{
	std::string str1("abcde");
	std::string str2;

	str2 = str1;

	std::cout << str2 << std::endl;
}

実行結果:

abcde

string を使うには、string という名前のヘッダをインクルードする必要があります。 string.h だとC言語の標準ヘッダの名前になってしまいますし、その C++版である cstring とも異なります。

string は C++標準ライブラリの1つなので、std名前空間内にあります。 ですから使うときは、std::string のように記述します。 前章で見たように、必要に応じて using(できれば using宣言) を行って記述を簡潔にすることはできます。

std::string は、int型のような型の一種だと考えて下さい。 ですから、std::string型の変数を宣言して使います。

std::string は、正確には std::basic_string というクラステンプレートに対する typedef です(第20章)。

main関数の最初の行にあるように、

std::string str1("abcde");

という風に書けば、std::string型の変数str1 が宣言され、初期値として "abcde" が格納されます。 少し不思議な構文に感じますが、C++ では一般的な記述です。 ちなみに、

std::string str1 = "abcde";

このように、C言語と同じような形で初期値を与えることもでき、意味は同じになります。

いずれも、引数が const char*型のコンストラクタを呼び出していることになります(第16章参照)。

また、

std::string str2;

このように初期値を与えなければ、空の文字列 "" で初期化されます。 つまり、未初期化のままな不定な状態にはなりません。 これは安全性が向上しているといえます。


続いて、次のようなコードがあります。

str2 = str1;

これは言うまでもなく、変数str1 の内容を 変数str2 へ代入しています。
char型の配列による実装ではこれはできず、strcpy関数(⇒リファレンス)を使いました(C言語編第25章)。 int型でも構造体型でも代入が可能なのに、文字列だけ代入できないのは直観的ではありませんでしたが、 std::string型を使えば、このように自然な形での代入が可能になります。

最後に、std::cout を使って標準出力へ出力しています。

std::cout << str2 << std::endl;

これも非常に簡単で、std::string型の変数をそのまま書けばいいだけです。


文字列同士の比較についても、strcmp関数(⇒リファレンス)のように関数を使うのではなく、 ==演算子や !=演算子が使えます。

#include <iostream>
#include <string>

int main()
{
	std::string s1("abcde");
	std::string s2("abcde");

	if (s1 == s2) {
		std::cout << "OK" << std::endl;
	}
	else {
		std::cout << "NG" << std::endl;
	}
}

実行結果:

OK


また、文字列の長さは length関数または size関数で取得できます。 これらの関数はどちらを使ってもまったく同じ結果になります。

#include <iostream>
#include <string>

int main()
{
	std::string str("abcde");

	std::cout << str.size() << std::endl;
	std::cout << str.length() << std::endl;
}

実行結果:

5
5

また、空文字列かどうかは empty関数で知ることができます。

#include <iostream>
#include <string>

int main()
{
	std::string str1("abcde");
	std::string str2;

	std::cout << str1.empty() << std::endl;
	std::cout << str2.empty() << std::endl;
}

実行結果:

0
1


C++ で文字列処理を行う際には、原則的には string を使うようにした方が良いです。 効率面では、char型配列を使うよりも若干劣ることがあるため、巨大なプログラムでは部分的に char型配列を使うようにして、 チューニングしなければならないこともあり得ますが、まずは安全で便利な string で作ることから始めた方がいいでしょう。 当サイトの C++編では今後、原則として string を使っていきます。

string に関するより詳細な機能は、【標準ライブラリ】第2章を参照して下さい。 (※ただし、【標準ライブラリ】第2章 には高度な内容も含まれている上、かなり詳細に渡って書かれており分量が多いので、深入りする必要はありません)。

ワイド文字列

ワイド文字列(C言語編第47章)を使いたい場合は、string の代わりに wstring を使います。

wstring そのものの機能は、string とまったく同一ですが、実用上は少し変更が必要です。
まず、"abcde" のように文字列の内容を記述する際には、L"abcde" のように L を付ける必要があります。 これはC言語のルールと同じです。
また、出力の際に std::cout ではなく std::wcout を使う必要があります。

実際に試してみます。

#include <iostream>
#include <string>

int main()
{
	std::wstring str1(L"abcde");
	std::wstring str2;

	str2 = str1;

	std::wcout << str2 << std::endl;
}

実行結果:

abcde

なお、1つのワイド文字は wchar_t型で表現されます。 wchar_t型は、C95規格以降のC言語にも存在しており(C言語編第47章参照)、 C言語のものは typedef で定義されています

typedef short int wchar_t;  // 定義例

そのため、この型を使うには、wchar.h をインクルードする必要がありましたが、 C++ の wchar_t は予約語として定義されています。 そのため、特定のヘッダをインクルードしなくても、常に使用できます。

文字列ストリーム

C言語には、sprintf関数(⇒リファレンス)や sscanf関数(⇒リファレンス)という標準関数があり、これらを使えば、 フォーマット指定した結果を文字の配列に書き込んだり、逆に文字の配列から読み取ったりすることができました。

string に対して書き込みを行うには、ostringstream を使います。 wstring であれば、wostringstream を使います。 例によって、両者の使い方は同一なので、前者だけを使って解説します。
ostringstream の使い方は簡単で、これまでに使ってきた std::cout と同じです。

#include <iostream>
#include <string>
#include <sstream>

int main()
{
	std::ostringstream oss;
	int num = 123;

	oss << "otringstream sample: " << num;
	
	std::string str(oss.str());

	std::cout << str << std::endl;
}

実行結果:

otringstream sample: 123

文字列ストリームの機能を使うには、sstream のインクルードが必要です。

std::ostringstream の変数を宣言し、それに対して <<演算子でデータを与えていきます。 上記のサンプルプログラムのように、複数一気に渡せますし、型の混在を気にする必要もありません。 また、内部で自動的に必要なメモリが確保されるので、バッファオーバーフローの心配もありません

std::ostringstream に渡したデータは、str関数を使って取り出すことができます。 普通の関数と構文が違っていますが、これは str関数がC言語的な関数とは違う形で実装されているためです。 この辺りはとりあえずそういうものだと思って進めて下さい。


逆に、string から内容を読み取るには、std::istringstream を使います。 wstring なら、std::wistringstream です。

#include <iostream>
#include <string>
#include <sstream>

int main()
{
	std::string str("abcde 100 2.34");

	std::istringstream iss(str);
	
	std::string s;
	int num;
	double d;
	iss >> s >> num >> d;

	std::cout << s << num << d << std::endl;
}

実行結果:

abcde1002.34

std::istringstream の場合は、変数宣言時に読み出し対象となる std::string を渡します。 すると、その std::string の内容がコピーされて記憶されます。 その後は、>>演算子を使って順次読み出しが行えます。 std::ostringstream が出力であったのに対し、 std::istringstream は入力になるので、シフト演算子の向きが反対になります
このような使い方の場合は、空白文字が現れるまでを1つの区切りとして動作します


ここまでに登場した、std::cout および各種文字列ストリームについては、 それぞれ、【標準ライブラリ】第27章以降で詳細に説明しています。
また、もう少し複雑なフォーマット指定を行う方法については次章でも取り上げます。

C++11 (UTF-8 の表現)

C++11

C++11 には、UTF-8 を表現するために u8プリフィックスが追加されています。 例えば、u8"xxx" と表記すると、この文字列リテラルは UTF-8 で表現され、 型としては char の配列になります
なお、u8'x' のような、文字リテラルは規格には存在しません。

UTF-8 は、ASCII で表現できる文字はそのままのコードで内包されているので、char型で扱えます。

VisualC++ 2013 は、u8プリフィックスに対応していませんが、2015/2017 は対応しています。 clang 3.7 は対応しています

C++11 (UTF-16、UTF-32 の表現)

C++11

C++11 には、char16_t型char32_t型という新たなワイド文字型が追加されています。 これらの型はそれぞれ、UTF-16、UTF-32 で表現される値を保持します。 wchar_t型と違い、明確に文字コードの種類が規定されているため、char16_t型は必ず 16bit ですし、 char32_t型は必ず 32bit です。

また、リテラルを表現する際、u'x' や u"xxx" のように、プリフィックスに u を付けると char16_t型になり、 U'x' や U"xxx" のように、プリフィックスに U を付けると char32_t型になります

なお、std::string を使いたい場合には、std::u16stringstd::u32string を使います (【標準ライブラリ】第2章)。

VisualC++ 2013 では、char16_t型、char32_t型ともに定義されていますが、u'x' や U'x' のような構文が使えないので、 結局のところ未対応です。2015/2017 では対応されています。
clang 3.7 では使用できます。

C++11 (生文字列リテラル)

C++11

文字列リテラルの中で、タブ文字や改行文字のような特殊な文字を使う場合、「\」によってエスケープさせる必要があります。 しかし、これによって非常に読み辛くなってしまうことがありました。
例えば次の例は、改行文字を入れ込んで、5行分の文字列を表現しています。

s = "abc\ndef\nghi\njkl\nmlo\n";  // 5行からなるメッセージ

C++11 では、エスケープを行うことなく、見た目通りに素直に文字を表現できる表現方法が追加されています。 この機能を使うと、上記の例を次のように書けます。

s = R"(abc
def
ghi
jkl
mno)";

「R"(」で始まり、「)"」で終える構文になります。括弧も必要になので注意して下さい。 この表記方法を、生文字列リテラルと呼びます。

生文字列リテラルでは、改行文字に「\n」を使うのではなく、本当に改行して書きます。 同様に、「\t」ではなくタブを入力すればいいですし、「\\」のように重ねずとも「\」で「\」を表現できます。

その代わり、終端がどこか分からないとコンパイラが困るので、「)"」という2文字を使えなくなってしまいます。 この問題を解決するため、開始と終端を表す文字の並びを指示することができるようになっています。 例えば次のように書きます。

s = R"delimiter(abc)delimiter";

「R"delimiter(」で始まり、「)delimiter"」で終える構文です。 delimiter と書いた部分は、16文字以内の任意の文字(ソースコード上で一般的に使える文字に限られます)を使えます。

なお、ワイド文字列であることを表すプリフィックス L のように、他のプリフィックスが必要であれば、 組み合わせて使うことができます。

s = LR"(abc)";
s = LR"delimiter(abc)delimiter";

生文字列リテラルは、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも使用できます。


練習問題

問題@ 次のプログラムを、std::string を使って書き換えて下さい。

#include <iostream>
#include <cstring>

void func(const char* s, std::size_t size);

int main()
{
	char s1[10] = "abcde";
	char s2[10] = "";

	func(s1, std::strlen(s1));
	func(s2, std::strlen(s2));

	std::strcpy(s2, "abcde");
	func(s2, std::strlen(s2));

	if (std::strcmp(s1, s2) == 0) {
		std::cout << "OK" << std::endl;
	}
}

void func(const char* s, std::size_t size)
{
	if (size > 0) {
		std::cout << s << " " << size << std::endl;
	}
	else {
		std::cout << "empty" << std::endl;
	}
}


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。
u8'x' のような、UTF-8 の文字リテラルは、C++11規格に存在しないので修正。

'2014/10/18 clang 3.2 に対応。

'2014/3/22 「C++11 (生文字列リテラル)」を追加。
第7章から、wchar_t型についての記述や、C++11 の UTF-8、UTF-16、UTF-32 を表現する方法についての記述を移動。

'2014/1/13 「ワイド文字列」のサンプルプログラムが、コンパイルエラーを起こしていたのを修正。

'2013/10/20 初期化に関する誤った記述を訂正。

'2013/10/5 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ