WisdomSoft - for your serial experiences.

9.8 文字列の編集

複数の文字列を結合したり、文字列の一部に別の文字列を挿入するような操作には文字列操作を行う標準関数を用います。

9.8.1 文字列の追加や変換処理

通常、多くの高水準言語は直観的な文字列操作機能を提供しています。人間の感性から考えれば、次のような文は文字列の追加処理であることを期待するでしょう。

"今日は " + 2002 + " 年 " + 11 + " 月 " + 18 + " 日です"

この式は、複数の文字列と数値から構成されています。高水準言語の多くは、式を最終的に文字列へと変換します。

"今日は 2002 年 11 月 18 日です"

多くのプログラマは、文字列の加算演算に対してこのような結果を望むことでしょう。しかし、C 言語の場合、文字列リテラルの実体は文字配列なので、配列の先頭へのポインタとして扱われます。そのため、文字列の編集を行うには直接配列を操作するしかありません。文字列を追加する場合は十分なメモリ領域を割り当て、文字列の末尾から、追加する文字列の各文字を順に代入していく必要があります。文字列の一部をカットしたり、数値と文字列の相互変換を行うときも同様です。

しかし、これらを全て自前で用意するのは効率的ではありません。そこで、標準ライブラリは文字列を操作するための基本的な関数群を用意しています。文字列関連関数は string.h ヘッダファイルで宣言されています。基本的な文字列の追加は strcat() 関数を使います。

strcan() 関数
char *strcat( char *string1, const char *string2 );

strcat() 関数は string1 に string2 を追加します。この関数の戻り値は string1 と同じもので、エラー値を返すようなことはありません。string1 も string2 も NULL で終わる文字配列でなければなりません。

関数は string1 の NULL 文字を string2 の先頭の文字列で上書きし、その後の文字列を追加していきます。string1 に割り当てられているメモリ領域が不十分だったり、string1 と string2 が重なり合っている場合の動作は未定義です。つまり、string1 と string2 は原則として異なる文字配列へのポインタでなければなりません。

コード1
#include <stdio.h>
#include <string.h>

int main() {
	char str1[256] = "Kitty on your lap ";
	strcat(str1 , "~あなたのひざの上の猫~");
	printf("%s\n" , str1);

	return 0;
}
実行結果
コード1 実行結果

コード1では、単純に str1 に異なる文字列を strcat() 関数を用いて追加しています。str1 は文字列を追加できるように余分に配列サイズを割り当てています。リテラル文字列などに文字列を追加しないように注意してください。

こうした処理を行うためには、文字列の文字数というものが重要になってきます。関数などが常に文字列の数を把握できるとは限りません。動的に文字列へのポインタから文字数を調べ、malloc() 関数でヒープを割り当て、これに文字列を編集するという処理はよく見かけられるものです。文字数を調べるには配列の先頭から調べ、NULL すなわち 0 の値を発見するまでカウントすることで実現できますが、この処理も標準関数がサポートしてくれています。文字数を取得するには strlen() 関数を使用します。

strlen() 関数
size_t strlen( const char *string );

string には文字数を調べたい NULL で終わる文字配列へのポインタを指定します。strlen() 関数は string の文字数を返します。この文字数には終端の NULL 文字は含まれないので注意してください。

コード2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
	const char *str1 = "Kitty on your lap ";
	const char *str2 = "~あなたのひざの上の猫~";
	char *str3 = (char*)malloc(strlen(str1) + strlen(str2) + 1);
	*str3 = 0;

	strcat(str3 , str1);
	strcat(str3 , str2);
	printf("%s\n" , str3);

	free(str3);
	return 0;
}
実行結果
コード2 実行結果

コード2では strlen() 関数を使って文字列へのポインタ str1 と str2 から文字数を調べています。文字数は malloc() 関数によるメモリの割り当てに用いられ、動的に文字の加算処理を行っています。str3 は、str1 と str2 の文字列を strcat() で追加するための十分なメモリ領域が割り当てられているはずです。このような処理を行えば、パラメータとして受け取った文字列へのポインタなど、関数の作成段階で文字数を静的に把握できない場合でも正しい文字列処理を行うことができます。

コード2の途中で、str3 に文字列 str1 を追加するために、str3 の先頭要素にわざわざ 0 (NULL 文字)を代入して strcat() 関数を使っていますが、これは正当なやり方とは思えません。最初の strcat(str3 , str1) は単純に str3 に str1 をコピーしているだけです。文字列のコピーを行う場合は strcpy() 関数を使ったほうがよいでしょう。

strcpy() 関数
char *strcpy( char *string1, const char *string2 );

この関数は、単純に string1 に string2 を NULL 文字を含めてコピーします。それ以外の動作は基本的に strcat() 関数と同じで、関数はコピー先の文字列 string1 を返します。string1 に割り当てられているメモリ領域が不十分だったり、string1 と string2 が重なり合っている場合の動作は未定義とされています。

ところで、こうした文字列の編集処理をプログラムで行う場合、if ステートメントなどで文字列の比較が求められるかもしれません。文字列へのポインタが同一かどうかを調べるのは簡単ですが、2つの NULL で終わる文字配列がまったく同じ内容の文字配列かを調べるには、strcmp() 関数を使います。

strcmp() 関数
int strcmp( const char *string1, const char *string2 );

string1 と string2 には比較する NULL で終わる文字列へのポインタを指定します。関数の戻り値が 0 の場合は2つの文字列が同一の文字列であることを表します。負数を返した場合は辞書順で string1 が string2 より小さく、正数を返した場合は string1 が string2 より大きいことを表します。

コード3
#include <stdio.h>
#include <string.h>

int main() {
	char str1[255], str2[255];

	printf("文字列を 2 つ入力してください>");
	scanf("%s %s", str1, str2);

	if (strcmp(str1 , str2) == 0)
		printf("%s と %s は等しい\n" , str1 , str2);
	else if(strcmp(str1 , str2) < 0)
		printf("%s は %s より小さい\n" , str1 , str2);
	else printf("%s は %s より大きい\n" , str1 , str2);

	return 0;
}
実行結果
コード3 実行結果 コード3 実行結果 コード3 実行結果

コード3は、コマンドライン引数から2つの文字列を入力します。すると、プログラムは入力された文字列を strcmp() 関数で比較し、その結果を標準出力に表示します。文字列を辞書順に整列させたり、同じ文字列かどうかを調べる時に strcmp() は威力を発揮するでしょう。

ここまで紹介した文字列関連関数は、単純な文字列の制御に頻繁に使われる関数ですが、複雑な処理となるとこれだけでは実現できないものがあります。例えば数値型の変数を文字列に変換して、他の文字列に追加したい場合は非常に手間がかかることでしょう。これを簡単に実現する方法があれば便利ですが、文字列関連関数にそのようなものはありません。

しかし、私たちはすでに高度な文字列の変換を行う関数を使っています。そう、printf() 関数や fprintf() 関数です。これらは書式制御文字列と可変個数のオプション引数を使って数値や浮動小数、文字などをひとつの文字列として出力することができました。printf() や fprintf() 関数は文字列をストリームに出力しましたが、メモリ上のバッファに出力することができれば、文字列への変換が printf() 関数のように行えるということになります。これを実現する関数が sprintf() 関数です。

sprintf() 関数
int sprintf( char *buffer, const char *format [, argument] ... );

buffer には文字列の出力先となるバッファへのポインタを、format には書式制御文字列を、argument にはオプション引数を指定します。format と argument については printf() 関数とまったく同じです。第1引数に出力先のポインタを指定する点で printf() 関数と異なっています。この関数を使えば、本来 printf() 関数が標準出力に表示するはずだった文字列をバッファに、すなわち十分な記憶領域が割り当てられている文字配列に出力することができます。

sprintf() 関数があることから予想ができますが sscanf() 関数も存在します。この関数はバッファから入力を行います。

sscanf() 関数
int sscanf( const char *buffer, const char *format [, argument ] ... );

この関数も、buffer にバッファへのポインタを指定します。format には書式制御文字列、argument はオプション引数を指定します。やはり、sprintf() 同様に第1引数にバッファへのポインタを指定する点を除いて scanf() 関数と同じです。

コード4
#include <stdio.h>

int main() {
	char str[256];
	sprintf(str , "今日は %d 年 %d 月 %d 日です" , 2002 , 11 , 18);
	printf("%s\n" , str);
	return 0;
}
実行結果
コード4 実行結果

コード4は sprintf() 関数を使って、これまで標準出力に文字列を表示してきた方法と同じ手法で文字列の編集を行っています。この方法であれば、数値や他の文字列などを一括して文字列に変換することができます。同様に、文字列を数値などに変換する場合は sscanf() 関数を使えばよいだけです。