文字列の扱い②
文字の取り扱いの詳細
ここまでで、基本的な文字の取り扱い方の詳細についてまとめてみます。
- 一般に文字を取り扱うときには、何文字かのつながり(文字列)で利用する
- 文字列を記憶するときには、文字型1次元配列を利用する
- 文字列を記憶する文字型1次元配列の大きさは、想定される文字長の最大+1で用意する
- 文字列を記憶させるときには、文字列の最後に何もない文字’\0’を記憶させる
- 文字はコンピュータの内部で数値で表されているので、その数値の大小関係を利用して文字の比較をすることができる
また、これらを踏まえた上で、文字列の読み込み・表示方法についても学習しました。
文字列の読み込み、ファイル入力方法の詳細
文字列の読み込みでは、実行時の読み込みとファイルからの読み込みで似た記述をします。処理の内容には大きな違いがあるので、注意が必要です。
■gets()の使い方
gets(配列名);
- 配列名は、「char 配列名[n];」として宣言された、n個の要素を持つ文字型1次元配列を意味する。
- 入力された文字の長さが、nよりも小さいときには入力された文字の最後に「何もない文字(’\0‘)」を入れたものが1次元配列に記憶される
- 入力された文字の長さが、nよりも大きい時には、n+1番目以降は無視して、先頭からn番目の文字までが、1次元配列に記憶される
■fgets()の使い方
fgets(文字配列名,[読み込む文字数],[ファイルポインタ]);
- ファイルポインタとは、「FILE *変数名」として変数宣言された変数を意味し、fopen()を行ったあとのファイルポインタを利用する
- 配列名は、「char 配列名[n];」として変数宣言された、n個の要素を持つ文字型1次元配列を意味する。
- ファイルの1行の長さが、[読み込む文字数]よりも小さいときは、改行を含む先頭の文字列が記憶される
- ファイルの1行の長さが、[読み込む文字数]よりも大きい時には、行の途中までの[読み込む文字数]分だけの文字列が配列に記憶され、次回の読み込みは前回の読み残した残りの文字からはじまる。
- nよりも大きな値を[読み込む文字数]として指定したときは、先頭からn文字だけが配列に記憶され、残りは切り捨てられる
文字列の読み込み、ファイル入力方法の詳細
次に文字列の表示方法についてまとめておきましょう。文字列の表示は、ファイルの出力の記述とよく似た記述をします。
■printf()の使い方
printf("%c", 変数名); printf("%s", 配列名);
- 1文字を表示する時は、「char 変数名;」として宣言されたものに対して、printf()に「%c」の変換仕様を利用する
- 文字列を表示する時は、「char 配列名[n];」として宣言されたものに対して、printf()に「%s」の変換仕様を利用する
- 文字列を表示するときは、表示する配列の先頭から何もない文字(‘\0’)までのすべての文字が左詰めで表示される(空白文字を含む)
■fprintf()の使い方
fprintf([ファイルポインタ], "%c", 変数名); fprintf([ファイルポインタ], "%s", 配列名);
- ファイルポインタとは「FILE *変数名;」として宣言された変数を意味し、fopenを行ったあとのファイルポインタを利用する
- 文字列をファイルに書き出すときは、「char 配列名[n];」として配列宣言されたものに対して、fprintf()「%s」の変換仕様を利用する
- 文字列を書き出す時は、書き出す配列の先頭から何もない文字(’\0’)までのすべての文字が左詰めで表示される(空白文字を含む)
文字列をコピーする
文字列をコピーしたいときは、#include <string.h>でstring.hファイルをインクルードしたあと、strcpy()(ストリングコピー)を使って次のように記述します。
strcpy(moji2, moji);
このように記述することで、mojiの配列内の文字をmoji2の配列にコピーします。このとき、moji2(コピー先)の文字列の長さ >= moji(コピー元)の文字列の長さ でないといけません。また、文字列を初期化したいときにも使うことができます。その場合は、以下のように記述します。
strcpy(moji, "Hello World");
この記述では、「”」(ダブルクォーテーション)で文字列(Hello World)を囲み、その文字列が文字列の1次元配列であるかのように扱っています。
文字列を繋ぐ
文字列を連結するにも関数があります。文字列のコピーと同様に#include <string.h>でstring.hファイルをインクルードしたあと、strcat()(ストリングキャット)という関数を使い、次のように記述します。
strcat(moji2, moji);
この記述では、moji2の後ろにmojiを追加することを表します。たったこれだけで、1文字ずつ調べながら代入するといった手間が省け簡単に2つの文字列を繋ぐことができます。
文字列を比較する
続いて文字列の比較です。文字列のコピーと同様に#include <string.h>でstring.hファイルをインクルードしたあと、strcmp()(ストリングコンペア)という関数を使い、次のように記述します。
if(strcmp(input, moji) == 0)
この記述では、inputとmojiの配列内の文字を比べ、等しければ0を返すという処理をしています。何十文字と膨大な文字を比較する時など有効に使える場面は数多くあります。
次のサンプルプログラムを実行して確認してみましょう。
#include <stdio.h>
#include <string.h>
int main(){
char input[3];
/*質問の表示と解答入力*/
printf("質問、正しいと思えばYes,誤っていると思えばNoと入力してください\n");
gets(input);
/*正解判定と結果表示*/
if(strcmp(input, "Yes") == 0){
printf("正解\n");
}else{
if(strcmp(input, "No") == 0){
printf("不正解\n");
}else{
printf("入力が不適切\n");
}
}
return(0);
}
何文字あるか調べる
文字列の長さを調べるには、strlen()(ストリングレングス)という関数を用います。これは、文字列を調べた結果を数値として返します。以下のように記述します。
n = strlen(moji);
この記述では、文字配列mojiの文字列の長さを調べ、その数値を変数nに代入する処理をしています。
次のサンプルプログラムを実行して確認してみましょう。
#include <stdio.h>
#include <string.h>
int main(){
char input1[10], input2[10];
/*質問の表示と解答入力*/
printf("1つめの文字列を入力");
gets(input1);
printf("2つめの文字列を入力");
gets(input2);
/*文字列を繋ぎ合わせるか判定*/
if((strlen(input1) + strlen(input2)) <= 10){
/*文字列を繋ぎ合わせ、結果表示*/
strcat(input1, input2);
printf("%s\n", input1);
}else{
/*結果の表示*/
printf("入力した文字列が長いので連結できません\n");
}
return(0);
}
文字列を使った応用場面
実際に現実的な例題で文字列処理の応用場面を見ていきましょう。
例題:アドレス帳の検索 10人の名前、電話番号が記述されているテキストファイルがあるとき、名前を入力したら、該当する電話番号が表示されるプログラムを作成します。
この例題に沿って、プログラムを作成していきましょう。
#include <stdio.h>
#include <string.h>
int main(){
char name[10][22]; //①
char phone[10][13];
char input[21];
int i;
FILE *FP;
/*ファイルのオープン*/
if((FP = fopen("address.txt", "r")) == NULL){
printf("ファイルが開けません\n");
return(1);
}
/*ファイルからデータを読み込む*/
for(i = 0; i < 10; i++){
fgets(name[i], 22, FP);
/*②読み込んだ文字の最後にある改行を消す*/
name[i][strlen(name[i])-1] = '\0';
printf("名前%d:%s\n",i+1 ,name[i]);
/*③電話番号の読み込み*/
fgets(phone[i], 13, FP);
/*読み込んだ文字の最後にある改行を消す*/
phone[i][strlen(phone[i])-1] = '\0';
}
/*電話番号を検索したい名前の入力*/
printf("電話番号を検索したい名前を入力してください\n");
gets(input);
/*検索と表示*/
for(i = 0; i < 10; i++){ //④
if(strcmp(name[i], input) == 0){
/*電話番号表示*/
printf("%sさんの電話番号は:%s\n", input, phone[i]);
}
}
fclose(FP);
return(0);
}
プログラム内の番号が付いている部分について順番に説明していきます。
①ファイルから名前と電話番号を読み込むための2次元配列と、名前入力の1次元配列を用意
ここで、注意するのは以下の点です。
- 文字列を扱う際に、関数gets()を利用するときには改行文字が文字列に読み込まれないため、用意する文字列の大きさは、「最大文字+1(‘\0’の分)」必要です。
- 文字列を扱う際に、関数fgets()を利用する時は、改行文字が文字列に読み込まれるため、用意する文字列の大きさは、「最大文字+2(‘改行文字と’\0’の分)」必要です。
②③文字列をファイルから読み込む
この部分は、どちらもファイルから読み込む記述です。ただ、fgets()で読み込んだ文字列は改行文字も含まれるので、strlen()を利用して文字列の長さを調べ、最後の改行文字を何もない文字’\0’に置き換えてます。
④読み込まれた文字列と入力された文字列の比較
ここでは、2つの文字列、ファイルから読み込んだ文字列name[X]とinputが同じであるかの比較をしています。
ファイルの中身を全て読み出す
ここまででは、ファイルからデータを読み出す時には、何行書かれているかあらかじめ知っておく必要がありました。実際には、ファイルに何行書かれているかわからないことがよくありますよね。次の例題から考えていきましょう。
例題:「read.txt」というテキストファイルに、何らかの情報が書かれています。これを「copy.txt」というファイルにコピーするプログラムを作成します。
ここで、問題となるのは、どれだけ繰り返せば良いのかです。コンピュータで扱っているテキストファイルは、ファイルの終わりが検出できるような、目に見えない特殊な文字が入っています。それは、EOF(End Of File)と呼ばれる文字です。プログラムでファイルを呼び出す場合には、このEOFを検出したり、検出する関数を利用したりしてプログラミングします。
次のサンプルを実行して確認してみましょう。
#include <stdio.h>
int main(){
char input[81];
FILE *FP1, *FP2;
/*ファイルから読み込みできるようにする*/
if((FP1 = fopen("read.txt", "r")) == NULL){
printf("読み込みファイルが開けません\n");
return(1);
}
/*ファイルに書き込み出来るようにする*/
if((FP2 = fopen("copy.txt", "w")) == NULL){
printf("書き込みファイルが開けません\n");
return(1);
}
/*繰り返し処理*/
while(fgets(input, 81, FP1) != NULL){
fprintf(FP2, "%s", input);
}
fclose(FP1);
fclose(FP2);
return(0);
}
これまで利用してきた関数ばかりなので、目当たりことはないですが、以下の部分に注目しましょう。
fgets(‥‥)!= NULL
この表現は、「もし、fgets()が、何もない文字を文字を出力しないならならば」という条件を表しています。今まで、利用してきたfgets()という関数は、
- ファイルの終端でないときには、NULLではない文字を出力する
- ファイルの終端であれば、NULLを出力する
という機能を持っている関数だったのです。この表現は重要なものなので、覚えておきましょう。