MSBOOKS

プログラミングとか深層学習とか日常の出来事とか

【C言語入門】part10 : 文字,文字列

前回は関数についてやりました.
今回は文字,文字列についてやっていきます.

文字,文字列とは?

今まで文字は少し触れていましたが,文字列について詳細の説明はしてきませんでした.文字と文字列の定義など,C言語で文字系の扱いは少し難しいところがあります.
f:id:msteacher:20190402200036p:plain
今回はこれらについて学んでいきましょう.
まず文字とは1文字だけを指します.例えば'a','K','+'などです.これらはchar型でそのまま定義することができます.ただし文字は'(シングルクォーテーション)で囲って定義します.

char moji = ’a’;

文字列とは複数の文字からなるものを指します.例えば"apple","input","1+1=2"などです.これらはchar型配列で定義することができます.このとき文字列は"(ダブルクォーテーション)で囲って定義します.

char mojiretsu[10] = "apple";

つまり文字列は文字型(char型)の集合であることがわかります.文字列の書式文字はpart2でやったように%sを用います(文字型は%c).そこで文字列を表示するプログラムを作成して,さらに配列の各要素を1つずつ取り出すプログラムを書いてみましょう.
・test10-1.c

#include <stdio.h>
int main(void){
   int i;
   char string[10] = "apple";
   printf("string=%s\n",string);
   for(i = 0; i < 10; i++){
      printf("string[%d]:%c\n",i,string[i]);
   }
   return 0;
}

・実行結果

>test10-1
string=apple
string[0]:a
string[1]:p
string[2]:p
string[3]:l
string[4]:e
string[5]:
string[6]:
string[7]:
string[8]:
string[9]:

このように配列の[5]以降は何も入っていません.しかしプログラム上ではどこが文字の終了かを明示的に示す必要があります.ここに表示されてはいませんが実はstring[5]には'/0'という終端文字が入っています.これが文字列の終了を示すものであり,文字型配列が文字数よりも多くてもここで文字が終わるよ!っと宣言することができます.初期値として定義するときはこの終端文字は自動で入ります.
終端文字を明示的に入れればその意味がわかります.以下のプログラムを実行してみましょう.
・test10-2.c

#include <stdio.h>
int main(void){
   int i;
   char string[10] = "app\0le";
   printf("string=%s\n",string);
   for(i = 0; i < 10; i++){
      printf("string[%d]:%c\n",i,string[i]);
   }
   return 0;
}

・実行結果

>test10-2
string=app
string[0]:a
string[1]:p
string[2]:p
string[3]:
string[4]:l
string[5]:e
string[6]:
string[7]:
string[8]:
string[9]:

文字列をそのまま表示するときにstring[3]に終端文字が入っているため,表示されるのは"app"だけになります.

文字列の長さ

文字配列をforで書くとその文字配列の中にどれだけ文字列が入っているかわからないため,とりあえず配列の大きさ分forでループさせるとtest10-2のように何もないところまで表示するといったことになってしまいます.そんなときに配列の長さが取得できればこういった無駄な処理はしなくて済みます.しかし配列の中で\0がどこに入っているかを探すのは手間がかかりますしプログラムの長さも長くなってしまいます.こんなときそれらを自動でやってくれる文字列に対する処理をまとめたものでstring.hというものがあります.これをincludeすることで文字列を操作する関数を利用することができます.文字の長さを取得するにはstrlen()関数を使います.まずは例を見てみましょう.
test10-3.c

#include <stdio.h>
#include <string.h> //追加
int main(void){
   int i;
   char string[10] = "apple";
   printf("string=%s\n",string);
   printf("strlen=%d\n",strlen(string));
   for(i = 0; i < strlen(string); i++){
      printf("string[%d]:%c\n",i,string[i]);
   }
   return 0;
}

・実行結果

>test10-3
string=apple
strlen=5
string[0]:a
string[1]:p
string[2]:p
string[3]:l
string[4]:e

このようにstrlen()を使うことで文字列の長さを取得することができ,処理の簡単化をすることができます.

文字列の結合,コピー

2つの文字列を結合する場合や、コピーする場合は配列を1文字ずつアクセスして結合したり、コピーするのは大変です。そんなときに先ほどのstring.hが輝きます。結合はstrcat,コピーはstrcpyを使って簡単に処理することができます。例を見ながら動作を見てみましょう。
・test10-4.c

#include <stdio.h>
#include <string.h>
int main(void){
   char string[10] = "Red";
   printf("string=%s\n",string);
   strcat(string,"Apple");
   printf("string=%s\n",string);
   return 0;
}

・実行結果

>test10-4
string=Red
string=RedApple

・test10-5.c

#include <stdio.h>
#include <string.h>
int main(void){
   char none[10] = "";
   char string[10] = "Red";
   printf("none=%s\n",none);
   printf("string=%s\n",string);
   strcpy(none,string);
   printf("string=%s\n",string);
   return 0;
}

・実行結果

>test10-5
none=
string=Red
string=Red

動作のイメージはつかめたでしょうか?まとめると以下のようになります。

//strcat
strcat(str1, str2);
//str1:結合先の文字列 str2:結合する文字列

//strcpy
strcpy(str1, str2);
//str1:コピー先の文字列 str2:コピーする文字列

文字列の比較

文字列を扱っていくと,文字列を比較したい場面が出てきます.たとえば,YES,NOで入力してくださいといった場面では文字列入力が必要となります.その入力された文字列がYESかNOかを判別するときに文字列の比較をします。これもfor文で1個ずつ比較するのは馬鹿らしいのでstring.hのstrcmpを使って比較をします.以下に例を示します.
・test10-6.c

#include <stdio.h>
#include <string.h>
int main(void){
   char string[10];
   printf("YESかNOを入力してね:");
   scanf("%s",string);
   if(strcmp(string,"YES")==0){
       printf("Yeah!!\n");
   }
   else if(strcmp(string,"NO")==0){
       printf("oh...\n");
   }
   else{
       printf("f〇ck you!!\n");
   }
   return 0;
}

・実行結果

>test10-6
YESかNOを入力してね:YES
Yeah!!

>test10-6
YESかNOを入力してね:NO
oh...

>test10-6
YESかNOを入力してね:GO
f〇ck you!!

まとめると以下のようになります.

//strcmp
ans=strcmp(str1,str2);
//str1,str2:比較文字
//ansが0のとき0
//str1>str2のとき正の値
//str2>str1のとき負の値

今回は文字と文字列,文字列操作について主な関数を紹介しました.
ゲームでは操作方法の提示や,キーワードの入力など様々なところでも使います.
文字列の概念を習得しておきましょう.お疲れさまでした.

練習問題

・問題1
 以下の要件を満たすミニゲームを作成してみよう.(test10-7.c)
Apple,Cherry,Peach,Melon,Grapeを文字型配列fruit[5][10]に入れる.
②乱数を生成して,文字型配列fruitの中身の並び方をシャッフルする(AppleをpApleのように二次元目の値はシャッフルしない).
③続いて1番目からなんの文字列が入っているかをstrcmpを使ってチェックしていく.ここで試行回数,間違えた場合は間違えた回数をカウントし,最後に何回間違えたか?試行回数中何回でクリアしたか?を表示する.
・実行結果例

>test10-7
1:Apple
2:Cherry
3:Peach
4:Melon
5:Grape
シャッフルします
1番目に入っている単語を入力してね:Cherry
間違い!
1番目に入っている単語を入力してね:Melon
正解!
2番目に入っている単語を入力してね:Grape
間違い!
2番目に入っている単語を入力してね:Cherry
正解!
3番目に入っている単語を入力してね:Apple
正解!
4番目に入っている単語を入力してね:Grape
間違い!
4番目に入っている単語を入力してね:Apple
間違い!
4番目に入っている単語を入力してね:Peach
正解!
5番目に入っている単語を入力してね:Grape
正解!
***********
試行回数:9
ミス回数:4
***********

練習問題解答例

・問題1
 ・test10-7.c

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
int main(void){
   char fruit[5][10] = {"Apple","Cherry","Peach","Melon","Grape"};
   int i,j;
   int miss=0,count=0;
   char input[10];
   char t[10];

   for(i=0; i<5; i++){
      printf("%d:%s\n",i+1,fruit[i]);
   }
   printf("シャッフルします\n");

   srand((unsigned)time(NULL));
   for(i=0; i<5; i++){
      j = rand() % 5;
      strcpy(t,fruit[i]);
      strcpy(fruit[i],fruit[j]);
      strcpy(fruit[j],t);
   }
   for(i=0; i<5; ){
      count++;
      printf("%d番目に入っている単語を入力してね:",i+1);
      scanf("%s",input);
      if(strcmp(input,fruit[i])==0){
         printf("正解!\n");
         i++;
      }
      else{
         printf("間違い!\n");
         miss++;
      }
   }
   printf("***********\n試行回数:%d\nミス回数:%d\n***********\n",count,miss);
   return 0;
}