Comments
Description
Transcript
文字列リテラル
6.文字列処理(教科書 p.301-p.332) 今回はC言語の文字列処理について復習し、文字列の探索手法について学ぶ。 文字列とは プログラム上での文字の並びを表すのが文字列(string)である。これは中身が空であっても 同様に呼ばれる。C言語では、"STRING"のように、文字の並びを二重引用符"で囲んだものを文字 列リテラル(string literal)と呼んでいた。ASCII コードではその内部は図6-1のようにな っている。 文字列リテラル"STRING" S T R I N G \0 各文字は記憶域上に連続的に配置され、 末尾にはナル文字が入る 図6-1 0 1 0 1 0 0 1 1 文字 'S' 0 1 0 1 0 1 0 0 文字 'T' 0 1 0 1 0 0 1 0 文字 'R' 0 1 0 0 1 0 0 1 文字 'I' 0 1 0 0 1 1 1 0 文字 'N' 0 1 0 0 0 1 1 1 文字 'G' 0 0 0 0 0 0 0 0 ナル文字 '\0' 文字列リテラル このように、各文字はメモリ上に連続して配置され、文字に対応する数値データによって管理 されている。各文字の対応する数値は文字コードの体系によって異なる。一般的に Linux では UTF が、Windows では SJIS と呼ばれる体系が使われている。char 型は今 8 ビットとして図示してある が、このサイズは処理系によっては 9 ビットや 32 ビットの場合もある。文字列リテラルには終端 を示すためのナル文字(null character)が自動的に付加される。ナル文字は文字コードによら ず、全てのビットが 0 である。文字列リテラルは定数として扱われる。文字を再変更する際の動 作はC言語では保証されていない。以下の宣言を見て欲しい。以下は文字列リテラルによって、 ポインタ pt を初期化しているが、文字列リテラル"1234"はこれだけで、メモリ上に確保されてい る。 char *pt = "1234"; ポインタ 0 1 2 3 4 1 2 3 4 \0 文字列リテラル ptは文字列リテラルの先頭文字'1'を指す 図6-2 ポインタと文字列リテラル 以下のプログラム(教科書 p.230、List8-4 の変更)で実際に利用してみる。ポインタを利用す る際にはそのポインタが指し示す実体が必要となるが、"1234"はどこに格納したかを記述してい ない。しかし、問題なく表示することができ、ポインタ変数の値を操作することで1文字ずつ出 力することもできる。しかし、*(pt+1)= '9';というような値の変更を行うことは C 言語では保証 されていない。 プログラム例 /* 文字列を表示(ポインタと文字列リテラル) */ #include <stdio.h> int main(void) { char *pt = "1234"; printf("ポインタptは\"%s\"を指しています。\n", pt); -1- printf("*pt は'%c'です。\n", printf("*(pt+1)は'%c'です。\n", printf("*(pt+2)は'%c'です。\n", printf("*(pt+3)は'%c'です。\n", *pt); *(pt+1)); *(pt+2)); *(pt+3)); return (0); } 実行例 ポインタptは"1234"を指しています。 *pt は'1'です。 *(pt+1)は'2'です。 *(pt+2)は'3'です。 *(pt+3)は'4'です。 もし、その文字列は変更される予定であれば、以下のように配列を確保した上で行う。 プログラム例 /* 文字列を格納して表示 */ #include <stdio.h> int main(void) { char st[10] = "ABCD"; printf("文字列stには\"%s\"が格納されています。\n", st); st[1] = 'D'; printf("文字列stには\"%s\"が格納されています。\n", st); return (0); } 実行例 文字列stには"ABCD"が格納されています。 文字列stには"ADCD"が格納されています。 文字列の長さ 配列を用いた文字列を利用することを考える。文字列の最後にはナル文字が入っている。文字 列の長さを求める場合、配列の先頭から始めて、ナル文字を線形探索していけばよい。その際、 ナル文字が見つかった添字が文字列の長さと一致する。 ナル文字を線形探索する 0 1 2 3 4 5 ⑥ S T R I N G \0 図6-3 7 8 9 文字列と長さ 文字列の長さを求めるプログラム(教科書 p.308、List8-6)を以下に転載する。 List8-6 /* 文字列の長さを求める */ -2- #include <stdio.h> /*--- 文字列 s の長さを求める(その1)---*/ int str_len(const char *s) { int len = 0; while (s[len]) len++; return len; } int main(void) { char str[256]; printf("文字列:"); scanf("%s", str); printf("その文字列は%d 文字です。\n", str_len(str)); return 0; } 実行例 文字列:ABCD その文字列は4文字です。 関数 int str_len(const char *s)を確認しよう。while の条件式は s[len]である。s[]は線形 探索を行おうとしている配列であり、len は走査するための変数である。char 型の配列の各要素 についての条件判断が行われているが、C言語では 0 以外の値が真とみなされ、ナル文字は文字 コードによらず、全てのビットが 0 であるため、このような比較方法でプログラミングが可能と なる。なお、C言語では、標準ライブラリの string.h として、strlen 関数(教科書 p.309 参照) が用意されており、これを使えば文字列の長さを求めることができる。 文字列から文字の探索 文字列からナル文字ではなく、任意の文字を探索する手続きを考える。例として文字列 "SURROUND"から文字'R'を探索するものとすると、図6-4のように、配列の先頭から線形探索を 行うことになる。 文字'R'を線形探索する 0 1 ② 3 4 5 6 7 8 S U R R O U N D \0 図6-4 このプログラム(教科書 p.234、List8-9)を以下に転載する。 List8-9 /* 文字列からの文字の探索 */ #include <stdio.h> -3- 9 /*--- 文字列 s から文字 c を探索 ---*/ int str_chr(const char *s, int c) { int i = 0; c = (char)c; while (s[i] != c) { if (s[i] == '\0') return -1; i++; } return i; /* 探索失敗 */ /* 探索成功 */ } int main(void) { char str[64]; char tmp[64]; int ch; int idx; /* この文字列から探索 */ /* 探す文字 */ printf("文字列:"); scanf("%s", str); printf("探す文字:"); scanf("%s", tmp); ch = tmp[0]; /* いったん文字列として読み込んで */ /* その最初の文字を探索文字とする */ if ((idx = str_chr(str, ch)) == -1) /* 先頭の出現を探索 */ printf("文字'%c'は文字列中に存在しません。\n", ch); else printf("文字'%c'は%d 文字目に存在します。\n", ch, idx + 1); return 0; } 実行例 文字列:SURROUND 探す文字:R 文字'R'は3文字目に存在します。 関数 str_chr は、文字列 s から文字 c を線形探索する。探索に成功時にはその位置の添字を返 し、失敗時には-1 を返す。なお、C言語では、標準ライブラリの string.h として、strchr 関数 と strrchr 関数(教科書 p.310 参照)が用意されており、文字列から文字の探索を行うことがで きる。 文字列の比較 C言語では、二つの文字列の大小関係を判定するため、標準ライブラリの string.h として、 strcmp 関数と strncmp 関数が提供されている。これらについて学習していく。 ◆strcmp 関数 この関数の仕様を以下に示す。 -4- ヘッダ 形式 解説 返却値 #include <string.h> int strcmp(const char *s1, const char *s2); s1 が指す文字列と s2 が指す文字列の大小関係(先頭から順に 1 文字ずつ比較していき、 異なる文字が出現したときに、それらの文字の対に成立する大小関係とする)の比較を 行う。 等しければ 0、s1 が s2 より大きければ正の整数値、s1 が s2 より小さければ負の整数 値を返す。 この関数では、図6-5に示すように、二つの文字列を先頭から順に比較して、すべての文字 が等しければ 0 を返す。 (a)文字列は一致 0 1 2 3 4 5 6 S T R I N G \0 0 1 2 3 4 5 6 S T R I N G \0 7 8 9 10 11 7 8 9 10 11 7 8 9 10 11 7 8 9 10 11 0を返す (b)文字列は不一致 0 1 2 3 4 5 6 S T R I N G \0 0 1 2 3 4 5 6 S T R I K E \0 0以外の値を返す 図6-5 strcmp 関数による比較 図(b)に示すように途中の文字が等しくない場合には 0 以外を返すことになるが、その値は文字 コード体系と処理系の両方に依存することなる。'N '- 'K 'の値を返却値として返す場合や'N ' が'K 'より大きいので単純に 1 を返す場合もある。 この strcmp 関数の仕様に準じて、同様の処理を行う str_cmp 関数の実装例(教科書 p.313、 List8-10)を以下に転載する。 List8-10 /* 文字列の比較 */ #include <stdio.h> #include <string.h> /*--- 二つの文字列 s1 と s2 を比較 ---*/ int str_cmp(const char *s1, const char *s2) { while (*s1 == *s2) { if (*s1 == '\0') /* 等しい */ return 0; s1++; s2++; } return (unsigned char)*s1 - (unsigned char)*s2; } int main(void) { char st[128]; puts("\"ABCD\"との比較を行います。"); -5- puts("\"XXXX\"で終了します。"); while (1) { printf("文字列 st:"); scanf("%s", st); if (strcmp("XXXX", st) == 0) break; printf("str_cmp(\"ABCD\", st) = %d\n", str_cmp("ABCD", st)); } return 0; } 実行例 "ABCD"との比較を行います。 "XXXX"で終了します。 文字列st:AX str_cmp("ABCD", st) = -22 文字列st:AA str_cmp("ABCD", st) = 1 文字列st:ABCD str_cmp("ABCD", st) = 0 文字列st:XXXX ◆strncmp 関数 この関数の仕様を以下に示す。 ヘッダ #include <string.h> 形式 int strncmp(const char *s1, const char *s2, size_t n); 解説 s1 が指す文字の配列と s2 が指す文字の配列の先頭 n 文字までの大小関係(先頭から順 に 1 文字ずつ比較していき、異なる文字が出現したときに、それらの文字の対に成立す る大小関係とする)の比較を行う。 返却値 等しければ 0、s1 が s2 より大きければ正の整数値、s1 が s2 より小さければ負の整数 値を返す。 この関数では、図6-6に示すように、二つの文字列を先頭から順に比較して、指定した文字 数までが一致しているかどうかを比較する。 (a)先頭3文字は一致 strncmp("STRING", "STRIKE", 3); 0 1 2 3 4 5 6 S T R I N G \0 0 1 2 3 4 5 6 S T R I K E \0 7 8 9 10 11 7 8 9 10 11 0を返す -6- (b)先頭5文字は不一致 strncmp("STRING", "STRIKE", 5); 0 1 2 3 4 5 6 S T R I N G \0 0 1 2 3 4 5 6 S T R I K E \0 7 8 9 10 11 7 8 9 10 11 0以外の値を返す 図6-6 strncmp 関数による比較 図6-6に示すように二つの文字列"STRING"と"STRIKE"の先頭 3 文字を比較すると一致するが、 先頭 5 文字を比較すると一致しないことがわかる。strncmp 関数を利用したプログラム例(教科 書 p.315、List8-11)を以下に転載する。 List8-11 /* 文字列の比較(strncmp 関数)*/ #include <stdio.h> #include <string.h> int main(void) { char st[128]; puts("\"STRING\"の先頭 3 文字と比較します。"); puts("\"XXXX\"で終了します。"); while (1) { printf("文字列 st:"); scanf("%s", st); if (strncmp("XXXX", st, 3) == 0) break; printf("strncmp(\"STRING\", st, 3) = %d\n", strncmp("STRING", st, 3)); } return 0; } 実行例 "STRING"の先頭3文字と比較します。 "XXXX"で終了します。 文字列st:STAR strncmp("STRING", st, 3) = 1 文字列st:STRIKE strncmp("STRING", st, 3) = 0 文字列st:XXXX 演習 課題 List8-6、List8-9、List8-10 を作成し、出力を確認せよ。 strncmp 関数と同じ動作をする以下の関数を作成せよ。 int str_ncmp(const char *s1, const char *s2, int n); 動作の確認は List8-11 に従い、main 関数を用いて、行うものとする。 -7-