●C++編(標準ライブラリ) 第1章 string

'2009/3/9 関数名の間違いを修正(rfind_last_of、rfind_last_not_of の先頭の r は不要)、at() の説明を追加、全体的に文章を見直し。
'2007/4/4 VisualC++2005 の警告対策を追加。

○stringとは

C++標準ライブラリには、stringというものが用意されています。stringは、次のように宣言されています。

typedef basic_string<char> string;

stringクラスという呼び方をすることがよくありますが、「string」というのは、 上のようにbasic_stringというテンプレートクラスを typedef したものですから、 厳密にはクラスではありません。

string はC言語では少々使いにくい文字列の操作を、より使いやすくします。 例えば従来は、文字列を代入するときに、

str = "abcde";           // これではうまくいかない
strcpy( str, "abcde" );  // ok

このように strcpy() というライブラリ関数の助けを借りる必要がありました。 (上の2行の意味が分からない人はC言語編第24章を参照して下さい)。

string であれば、上のような文字列の代入が、

str = "abcde";  // str が string なら ok

というように =演算子で行えます。string が basic_stringクラスの typedef であることから気が付く人もいると思いますが、 =演算子はオーバーロード(演算子のオーバーロードについては、C++編第17章参照)されているのです。 string は特殊な方法を使っている訳ではなく、内部的には従来通りの char*型の文字列が使われています。 string を使う側には、中で何をしているのかは分かりませんが、とにかく都合がよいように動作してくれるのです。

○基本操作

string には様々な能力がありますが、とりあえず基本的なところだけ紹介します。 まず簡単なサンプルをみて下さい。

#include <string>
#include <iostream>

int main()
{
	std::string str1;             // 空のstring
	std::string str2( "abcde" );  // 初期値として"abcde"

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

	str1 = str2;  // str2の内容をstr1に代入
	std::cout << str1 << std::endl;
	return 0;
}

繰り返しになりますが、string は basic_stringクラスを typedef したものなので、結局のところクラスのように扱えます。 インスタンス化するときにコンストラクタが呼び出されますが、引数無しのコンストラクタは、空の文字列で初期化を行います。 上の str2 のように、引数付きのコンストラクタを呼べば、初期値を与えられます

さらに、char型の配列を使う場合と異なり、宣言する段階で格納する文字数を指定する必要がありません。 素晴らしいことに、string は内部で自動的にメモリを確保してくれます。 そのため、どれくらいの文字数が必要になるか分からない場合でも効率よく使用できます。

代入については最初に説明したように =演算子で行えます。 また、std::coutを使えば、そのまま出力することができます。

上のプログラムを見てわかるように、string を使うには、stringという名前のヘッダファイルをインクルードしなければなりません。 「string.h」のように拡張子を付けてしまうと、C言語の標準ヘッダの名前になってしまいます。

また、string は cout等と同様、std という名前空間(C++編第18章参照)に含まれているので、「std::」で修飾して使います。


次のサンプルを見て下さい。

#include <string>
#include <iostream>

int main()
{
	using namespace std;

	string str1( "abcde" );
	string str2( "fghij" );

	str1 += str2;   // str1の末尾にstr2を連結
	cout << str1 << endl;

	if( str1 == "abcdefghij" )  // 2つの文字列を比較
	{
		cout << "一致" << endl;
	}
	else
	{
		cout << "不一致" << endl;
	}

	cout << "str1の文字数は" << str1.size() << endl; // 文字列の長さを出力
	return 0;
}

新しい使い方が3つ登場しています。

+=演算子を使うと、文字列を連結できます。 +演算子を使っても同じことです。 この動作は strcat関数と同じですが、strcat関数と違い、連結結果の長さを気にする必要がありません。 連結結果を受け取る側の stringインスタンスの容量が足りていなければ、自動的にメモリを確保してくれます。

==演算子を使えば、2つの文字列が同じものかを調べられます。これは strcmp関数の代わりになります。 もちろん、!=演算子や <演算子なども同じように利用できます。 不等号の演算子は、辞書順の比較を行う点も strcmp関数と同じです。

最後に、strlen関数の代わりですが、これはsize()というメンバ関数が対応します。 length() というメンバ関数もありますが、動作は全く同じです。

size() に関連して、empty() というメンバ関数もあります。 これは、文字列が空かどうか調べるためのものです。空かどうか調べる場合には、

if( str.size() == 0 )

よりも、

if( str.empty() )

を使う方が効率が良いとされています。

○従来の文字列への変換

いくら string が便利だからといって、プログラム全体の文字列処理を string しか使わないように書き換えられるかというと、少し問題もあります。 特に、C言語の標準ライブラリ関数の引数は char*型を使っているので、こういった関数に文字列を渡すときに困ります。 例えば、fopen関数の引数は string型ではありませんね。

string型で管理している文字列を、char*型を要求する関数に渡すような場合、c_str() というメンバ関数を使います。

#define _CRT_SECURE_NO_DEPRECATE 1  /* VisualC++2005 での警告抑制 */
#include <string>
#include <iostream>

int main()
{
	using namespace std;

	string filename( "test.txt" );
	
	FILE* fp = std::fopen( filename.c_str(), "r" );
	
	// 省略	
	
	std::fclose( fp );

	return 0;
}

c_str() を使ったとき返される文字列の末尾には '\0' が付加されています。 そのため、C言語で使ってきた通常の文字列の形式と一致します。 だから、fopen関数などの引数として使用できます。 なお、空の文字列を保持している string型に対してc_str()を呼び出すと、'\0' へのポインタを返します

○要素の直接アクセス

char型を使って文字列を表す場合、それは char型の配列という扱いになります。そのため、

str[5] = 'a';
str[index] = 'b';

のように添字を使ってアクセスできます。string型を使った場合も、これと同じことができます。

str[5] = 'a';
str[index] = 'b';

見て分かる通り、全く同じ方法でアクセスできています。 これは []演算子がオーバーロードされているため可能なことです。 string型は、保持する文字列の文字数の制限を持ちませんが、[]内に指定した値が、現在の文字数を超えてはいけません。 これは char型の配列と同じことで、もし範囲外をアクセスしたら、実行時エラーによってプログラムが強制終了する可能性があります。

at() というメンバ関数を使うと、[] と同じように要素をアクセスできます。 at() の場合、範囲外をアクセスすると out_of_range例外を送出します (out_of_range例外については第27章参照、例外機構については言語解説編第25章参照)。

str.at(5) = 'a';
str.at(index) = 'b';

○部分文字列

任意の文字列から、部分的な文字列を取り出すことができます。 これにはsubstr() というメンバ関数を使います。

#include <string>
#include <iostream>

int main()
{
	using namespace std;

	string str( "abcdefghijk" );
	str.substr( 5 );    // "fghijk"
	str.substr( 5, 3 ); // "fgh"

	return 0;
}

上のように使います。 引数が1つの場合と2つの場合とがありますが、いずれの場合も第1引数が、取り出す部分文字列の最初の文字が、元の文字列の何番目の文字であるかです。 上の場合、5なので、5文字目(先頭は0文字目である)の 'f' が最初の文字になります。

第2引数の方は、何文字分取り出すかを指定します。 上の例だと、5文字目から3文字分取り出すことになるので、"fgh" が取り出される結果になります。 「何文字目まで」ではなく「何文字分か」なので注意して下さい。 第2引数を省略すると、文字列の末尾まで取り出します。

○検索

文字列中から、目的の文字列を探すことができます。文字列検索用の関数は6種類用意されています。 それぞれ、検索のされ方が異なりますが、使い方自体は同じです。

それぞれ、引数として目的の文字列を指定します。戻り値は、上記の説明に応じた位置が返されます。 位置というのは、何文字目であるかという意味ですので、[]演算子で指定する添字と同じものです。 もし、一致する位置が発見されなければ、string::npos という値が返されます。 string::nposは、-1と定義されています

#include <string>
#include <iostream>

int main()
{
	using namespace std;

	string str( "abcdefghijk" );
	string::size_type index = str.find( "fgh" );  // "fgh"を検索
	if( index == string::npos )  // 検索できたかどうか
	{
		cout << "検索に失敗しました" << endl;
	}
	else
	{
		cout << str.substr( index ) << endl;
	}

	return 0;
}

string::size_type という型が登場していますが、検索の各種関数の戻り値は、この型で受け取ることになっています。 実際には int型などで受け取ることもできますが、string::npos との比較は、string::size_type型としなければなりません。


C++編(標準ライブラリ)のトップページに戻る

サイトのトップページに戻る