文字と文字列

文字

文字は1つの文字、文字列は文字がいくつか並んだものをいいます。

C言語の古い仕様では英字(A-Zの大文字小文字)と数字、若干の記号だけが文字です。つまり文字は1バイトで、2バイト以上は文字列になります。

最近は国際化が進みユニコードを使って日本語でも中国語でも文字を同様の考え方で認識できるようになってきましたがこの仕様はC言語の仕様の中に色濃く残っています。

1バイト文字を格納する変数 char

int は整数、float は浮動小数点数、そして char は文字型(キャラクタcharacter)です。

int a;
float b;
char c;
char d = 'A';

代入ではシングルクォート ( ' ) で囲みます。文字コードが1バイトで表される文字に限ります。英字(A-Zの大文字小文字)と数字、若干の記号の1文字です。

C言語では char の実体は文字コードを格納する1バイトの整数です。

1バイトで表現できるのは0から255までですが、ユニコード(UTF-8)では128以上は複数バイトで表現される文字に使われますから、表示しようとした時どのように扱われるかはシステムに依存します。また0から127の範囲でも改行とかタブといった制御文字がありますから画面に表示するときには注意が必要です。

課題8-1 文字を使ってみる

プログラム名 k0801.c

 1: /* 文字を使ってみる k0801.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     char moji1 = 'A';
 7:     char moji2 = 'a';
 8:     printf( "%c\n",moji1 );
 9:     if ( moji1 > moji2 ){
10:         printf( "%c の方が %c より大きい\n",moji1,moji2 );
11:     }else{
12:         printf( "%c の方が %c より小さい\n",moji1,moji2 );
13:     }
14:     return 0;
15: }

プログラム解説 k0801.c

6:,7:
宣言と代入を同時にしています。文字の値をプログラム中に書くにはシングルクォートで囲む必要があります。
囲まないと、undeclared (first use in this function) などとエラーが出ます。囲まれていない文字は変数だと判断され、その変数がそれ以前に宣言されていないので 定義されていない(初めてでてきた変数)と報告したのです。
8:
%c に注目。charを表示するときはこれです。
9:
char の大きさは比較できます。文字コードを数値として比較するだけです。文字コードをどう決めているかによります。よほど特殊なコンピュータでない限りASCIIコードと互換なコードを使用していますから、たぶんどこでも同じ結果になるでしょうが絶対とはいいきれません。

実行結果はこうなります。

~/c$ ./k0801
A
A の方が a より小さい

課題8-2 ちょっと失敗をしてみる

k0801.cに比べて色のついた部分が異なっています。

プログラム名 k0802.c

 1: /* ちょっと失敗をしてみる k0802.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     int a = 120;
 7:     char moji1 = 'A';
 8:     char moji2 = a;
 9:     printf( "%c\n",moji1 );
10:     if ( moji1 > moji2 ){
11:         printf( "%c の方が %c より大きい\n",moji1,moji2 );
12:     }else{
13:         printf( "%c の方が %c より小さい\n",moji1,moji2 );
14:     }
15:     return 0;
16: }

プログラム解説 k0802.c

6:
変数 a を整数と宣言し、120を代入しました。
8:
char moji2 = 'a'と書くつもりが、シングルクォートを忘れてchar moji2 = aと書いてしまいました。

間違えていますが、コンパイルは通ります。

実行結果は次のようになります。

~/c$ ./k0802
A
A の方が x より小さい

moji2 の値が 'x' になっています。 'a' は文字ですが、 a は変数名と判断され、今回はたまたま a という変数があったのでエラーになりませんでした。

その上 char は実体が整数なので、a の値である120の代入が正常にできてしまいます。

そして120を文字コードとして解釈すると 'x' になったということです。

ASCIIコード表

40+1 =41 が A の文字コードです(この数値は16進数)。

つまり左にある16進数と上の16進数を加えたものが交点の文字コードです。

C言語では 0x41 と0xを前に置くことで16進数を直接プログラム中に書くことができます。

制御文字は相手により設計により機能が異なりますので全部が有効ではありません。

ASCIIコードの文字表
  +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
00 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
10 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
20 SP ! " # $ % & ' ( ) * + , - . /
30 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
40 @ A B C D E F G H I J K L M N O
50 P Q R S T U V W X Y Z [ \ ] ^ _
60 ` a b c d e f g h i j k l m n o
70 p q r s t u v w x y z { | } ~ DEL

課題8-3 文字コードを表示

 1: /* 文字コードを表示: k0803.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     char moji1 = 'A';
 7:     printf( "%c は 10進で%3d 16進で%x\n",moji1,moji1,moji1 );
 8:     moji1 = 'a';
 9:     printf( "%c は 10進で%3d 16進で%x\n",moji1,moji1,moji1 );
10:     moji1 = '~';
11:     printf( "%c は 10進で%3d 16進で%x\n",moji1,moji1,moji1 );
12:     return 0;
13: }

実行結果はこうなります。

~/c$ ./k0803
A は 10進で 65 16進で41
a は 10進で 97 16進で61
~ は 10進で126 16進で7e

課題8-4 キー入力を受ける

1バイト文字しか使えないcharが活躍する場面はキー入力を受け取るときでしょう。

「A ですか B ですか C ですか」と表示してキーボードから入力してもらうプログラムを作ります。

プログラム名 k0804.c

 1: /* キー入力を受ける : k0804.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     char a;
 7:     printf( "A ですか B ですか C ですか \n" );
 8:     scanf("%c", &a);
 9:     if ( a == 'A' ){
10:         printf( "A にしました。\n" );
11:     }else if ( a == 'B' ){
12:         printf( "B にしました。\n" );
13:     }else{
14:         printf( "C にしました。\n" );
15:     }
16:     return 0;
17: }

プログラム解説 k0804.c

8: scanf("%c", &a);
printf("%c",a);で文字を画面に出したのと同じような仕組みでcharをキーボードから受け取ります。キーボードからの入力がなければ待ちます。
1文字の入力ですが、Enterキーが押されないと入力があったことを感知しません。
入力されたデータを a に代入するには、&a と書きます。この仕組みはあとで出てきます。ここではこうするものだと考えてください
9:-
入力された文字が 'A' だったらば 「Aにしました」、'B' ならば「Bにしました」、その他なら「Cにしました」と表示します。
大文字と小文字を区別します。'a' だと 「その他」の扱いになります。

実行結果は

~/c$ ./k0804
A ですか B ですか C ですか 
B
B にしました。

Bはキー入力の時に同時に画面に表示されるもので、エコーバックといいます。文字列の入力の時にどこまで入力したかわからなくならないようにしています。また、[Enter]キーを押すまでは[Backspace]キーで修正することができます。

A,B 以外では C になることも確認してください。

~/c$ ./k0804
A ですか B ですか C ですか 
a
C にしました。

課題8-5 小文字でも受け付ける

課題8-4 を改良して小文字でも受け付けるようにします。

[ 考えてください ]の部分は 9: を参考に書き換えてください。

プログラム名 k0805.c

 1: /* 小文字でも受け付ける : k0805.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     char a;
 7:     printf( "A ですか B ですか C ですか \n" );
 8:     scanf("%c", &a);
 9:     if ( a == 'A' || a == 'a' ){
10:         printf( "A にしました。\n" );
11:     }else if ( a == 'B' [ 考えてください ]  ){
12:         printf( "B にしました。\n" );
13:     }else{
14:          printf( "C にしました。\n" );
15:     }
16:     return 0;
17: }
9:
|| は「または」を示す論理演算子でした。whileで繰り返しで説明しています。優先順位により == の比較演算子のほうが先に判断されますが、自信がなければ括弧をつければよいでしょう。
( a == 'A') || (a == 'a')

実行結果は

~/c$ ./k0805
A ですか B ですか C ですか 
b
B にしました。
~/c$ ./k0805
A ですか B ですか C ですか 
a
A にしました。
~/c$ ./k0805
A ですか B ですか C ですか 
f
C にしました。

課題8-6 ABC以外は受け付けない

AB以外のキー入力がCになってしまうのはちょっと…

プログラム名 k0806.c

 1: /* ABC以外は受け付けない: k0806.c */
 2: #include <stdio.h>
 3:
 4: int main()
 5: {
 6:     char a;
 7:     char abc = 'X';
 8:     while( abc!='A' && abc!='B' && abc!='C' ){
 9:         printf( "A ですか B ですか C ですか \n" );
10:         scanf("%c", &a);
11:         if ( a == 'A' || a == 'a' ){
12:             abc = 'A';
13:         }else if ( a == 'B' || a == 'b'  ){
14:             abc = 'B';
15:         }else if ( a == 'C' || a == 'c'  ){
16:             abc = 'C';
17:         }
18:     }
19:     printf( "%c にしました。\n",abc );
20:     return 0;
21: }
7:
abcが結果を入れる変数です。AかBかCが入って欲しいので名前がabcです。
8:
&& は「かつ」を示す論理演算子でした。whileで繰り返しで説明しています。 優先順位により == の比較演算子のほうが先に判断されますが、自信がなければ括弧をつければよいでしょう。
( abc != 'A') && (abc != 'B') && (abc != 'C')
abcの内容が AでもBでもCでもない間繰り返すという条件になっています。
11:-17:
aは入力された文字を仮に入れるところとして使っている変数です。小文字でも受け付けるために || を使っています。 abcには大文字にしたものを代入しています。
elseがないので、入力が A,a,B,b,C,c 以外の時は何もしません。abcの値は 'X' のままです。それでwhileで繰り返しになります。

実行結果は

~/c$ ./k0806
A ですか B ですか C ですか 
v
A ですか B ですか C ですか 
A ですか B ですか C ですか 
b
B にしました。

結果はちょっと奇妙に思えるかもしれません。「A ですか B ですか C ですか 」を繰り返している部分があるからです。 v の一文字しか入れていないのに2回でます。

では複数の文字を入力したらどうなるでしょう。

~/c$ ./k0806
A ですか B ですか C ですか 
xyz
A ですか B ですか C ですか 
A ですか B ですか C ですか 
A ですか B ですか C ですか 
A ですか B ですか C ですか 
seiai
A ですか B ですか C ですか 
A ですか B ですか C ですか 
A ですか B ですか C ですか 
A にしました。

xyzの3文字を入れると4回でてきます。入力された文字数+1のようです。

seiaiの時は3つです。これは seiai の中に a が含まれているからです。 seiの3文字に対して「A ですか B ですか C ですか 」を出して、次の a でwhileを終えているのです。

課題8-7 入力された文字のコードを表示

キーボードから1文字ずつ受け取って表示し、さらにキー入力を待つプログラムです。

小文字の z を入力すると終了します。

プログラム名 k0807.c

 1: /* 入力された文字のコードを表示: k0807.c */
 2: #include <stdio.h>
 3: 
 4: int main()
 5: {
 6:     char a = 'o';
 7:     printf( "入力した文字を文字コードと共に表示します \n" );
 8:     while( a!='z' ){
 9:         scanf("%c", &a);
10:         printf( "%c 文字コードは %2x です。\n",a,a );
11:     }
12:     printf( "%c でした。\n",a );
13:     return 0;
14: }
15: 

まず v と入力してみます。

~/c$ ./k0807
入力した文字を文字コードと共に表示します 
v
v 文字コードは 76 です。

 文字コードは  a です。

プログラムはまだ終わっていません。でも2文字表示されました。一つは v ですが、もうひとつは文字コード a です。上の表では LF です。これは改行を意味します。%c でこれを画面に表示したので1行空いています。これはEnterキーで入力されたものです。

z を入れると終了します。

z
z 文字コードは 7a です。
z でした。

このときにも、z の後には LF が入っているのですが、z を受け取ったときに while を終了してしまうので、改行文字は表示されません。

ではもっとたくさん文字を入れてみます。qwerty と ozone を入れて見ました。実行結果は

~/c$ ./k0807
入力した文字を文字コードと共に表示します 
qwerty
q 文字コードは 71 です。
w 文字コードは 77 です。
e 文字コードは 65 です。
r 文字コードは 72 です。
t 文字コードは 74 です。
y 文字コードは 79 です。

 文字コードは  a です。
ozone
o 文字コードは 6f です。
z 文字コードは 7a です。
z でした。

このことから、キーボードからの入力は一度別の場所に保存されていて、scanf("%c", &a);では それを一文字ずつ持ってきていることがわかります。

文字列は文字の配列

C言語では文字列を入れるための変数がありません。文字列は文字の配列として扱います。

配列は宣言時ならばまとめて代入が可能でした。文字の場合も可能ですが、約束事があります。

C言語では文字列の終わりには文字コード00の文字(ヌル文字)を入れることになっています。文字としてプログラム中に書くときには'\0'とします。

char s1[7] = { 'a','b','c','d','e','f','\0' };
char s2[] = { 'a','b','c','d','e','f','\0' };

C言語の配列は要素数を省略すると必要数が確保されます。

実はこれまで文字列をダブルクォートでくくって使っていました。これも使えます。

char s3[7] = "123456";
char s4[] = "ABCDEF";

この方法では'\0'は自動でつきますが、要素数を指定するときは'\0'が加わるために文字数+1以上にしなければならないことに注意が必要です。文字数+1以上に大きい場合は問題ありませんが'\0'より後ろに何が入るかはコンパイラによります。

改行がなくてもいいなら文字の配列はそのままprintfの引数にできます。printfの書式の中に書くときは文字列は %s で指定します。

char s3[7] = "123456";
char s4[] = "ABCDEF";
printf( s3 );
printf( "%s\n",s4 );

課題8-8 文字列のテスト(1)

文字列を文字の配列に入れてそのまま表示するのは次のプログラムの内容だけ知っていれば用が足ります。

プログラム名 k0808.c

 1: /* 文字列のテスト(1): k0808.c */
 2: #include <stdio.h>
 3:
 4: int main()
 5: {
 6:     char s1[] = { 'a','b','c','d','e','f','\0' };
 7:     char s2[] = "0123456789";
 8:     char s3[] = "ABCDEF";
 9:     printf( "%s \n",s1 );
10:     printf( "%s %s\n",s2,s3 );
11:     return 0;
12: }

実行結果は

~/c$ ./k0808.c 
abcdef 
0123456789 ABCDEF

課題8-9 文字列のテスト(2)

配列として個々の文字を取り出したみたのが11:からの部分です。

プログラム名 k0809.c

 1: /* 文字列のテスト(2): k0809.c */
 2: #include <stdio.h>
 3:
 4: int main()
 5: {
 6:     char s1[] = { 'a','b','c','d','e','f','\0' };
 7:     char s2[] = "0123456789";
 8:     char s3[] = "ABCDEF";
 9:     printf( "%s \n",s1 );
10:     printf( "%s %s\n",s2,s3 );
11:     int i;
12:     char c,d;
13:     for(i=0;11>i;i++){
14:         c = s2[i];
15:         d = s3[i];
16:         printf( "%2d 番目の文字のコードは %10x %10x です。\n",i,c,d );
17:     }
18:     return 0;
19: }

実行結果は

~/c$ ./k0809.c 
abcdef 
0123456789 ABCDEF
 0 番目の文字のコードは         30         41 です。
 1 番目の文字のコードは         31         42 です。
 2 番目の文字のコードは         32         43 です。
 3 番目の文字のコードは         33         44 です。
 4 番目の文字のコードは         34         45 です。
 5 番目の文字のコードは         35         46 です。
 6 番目の文字のコードは         36          0 です。
 7 番目の文字のコードは         37          0 です。
 8 番目の文字のコードは         38         15 です。
 9 番目の文字のコードは         39          6 です。
10 番目の文字のコードは          0         40 です。

30,31,32,33,34...と縦に並んでいるのが s2[] の文字列のコードで、最後にヌル文字 0 が入っているのが分かります。

41,42,43,44...と縦に並んでいるのが s3[] の文字列のコードです。s3[5]が46なので 'F' を表し、s3[6]がヌル文字ですが、その後もないはずの s3[7]以下の要素を表示できてしまいます。ここに何が入るかはコンパイルのときの状況によります。s1,s2の値が見えることもあります。

配列の要素数以上のデータを読み出せてしまうのはC言語が配列の長さのチェックをしないことによります。 配列の長さを意識してプログラムしないと失敗をします。特に書き込みは危険です。

また、日本語は複数バイトで1文字を表現しますから個々の文字を取り出してcharで扱うことはできません。

ここでは文字コードで配列に格納されていることの確認とヌル文字の観察に止めておきます。

文字列の関数

実は次のような他言語でも簡単にできそうなことがCではできません

char s1[] = "123456";
char s2[] = "ABCDEF";
s1 = "abcd";   /* できません */
s3 = s1 + s2;  /* できません */
strcpy(str1, str2) str2をstr1にコピーする。('\0'を含む。str1に十分な長さがなければデータは破壊される)
strcat(str1, str2) str1の後にstr2を連結する。(連結後に'\0'を加える。str1に十分な長さがなければデータは破壊される)
strlen(str) 文字列strの長さを取得する。(返り値は符号なしのintだがintと考えて良い)
strcmp(str1, str2) str2とstr1を比較する。(返り値はint。正ならばstr1>str2、負ならばstr1<str2、0ならば等しい)

これらの関数を使うには、#include <string.h> を書き加える必要があります。 incompatible implicit declaration of built-in function ‘strcpy’ などと警告を受けます。

strcpy,strcatではstr2の方は変化しません。str1が書き換えられます。str1に十分な長さが確保されていない時でも、'\0'まで構わずにコピーしてしまう可能性があります。その場合、はみ出した部分が他の変数のデータを破壊します。十分注意が必要です。

略する前の単語を知っていれば覚えやすいかもしれません。

str : string
cpy : copy
cat : concatenate
len : length
cmp : compare

課題8-10 文字列の関数

文字列の関数必要なinclude文です。

プログラム名 k0810.c

 1: /* 文字列の関数: k0810.c */
 2: #include <stdio.h>
 3: #include <string.h>
 4: 
 5: int main()
 6: {
 7:     char s1[] = "0123456789";
 8:     char s2[] = "ABCDEF";
 9:     char s3[20];
10:     strcpy(s3,s1);
11:     strcpy(s1,"abcd");
12:     printf( "s1 の内容は %s 長さは %d\n",s1,strlen(s1) );
13:     printf( "s2 の内容は %s 長さは %d\n",s2,strlen(s2) );
14:     printf( "s3 の内容は %s 長さは %d\n",s3,strlen(s3) );
15:     strcat(s3,s2);
16:     printf( "--strcat(s3,s2)しました--\n" );
17:     printf( "s2 の内容は %s 長さは %d\n",s2,strlen(s2) );
18:     printf( "s3 の内容は %s 長さは %d\n",s3,strlen(s3) );
19:     int i;
20:     i=strcmp(s1,s2);
21:     printf( "%s と %s を比較すると %d です。\n",s1,s2,i );
22:     i=strcmp(s2,s3);
23:     printf( "%s と %s を比較すると %d です。\n",s2,s3,i );
24:     return 0;
25: }

実行結果は

~/c$ ./k0810
s1 の内容は abcd 長さは 4
s2 の内容は ABCDEF 長さは 6
s3 の内容は 0123456789 長さは 10
--strcat(s3,s2)しました--
s2 の内容は ABCDEF 長さは 6
s3 の内容は 0123456789ABCDEF 長さは 16
abcd と ABCDEF を比較すると 32 です。
ABCDEF と 0123456789ABCDEF を比較すると 17 です。

課題8-11 比較と日本語の文字

プログラム名 k0811.c

 1: /* 比較と日本語の文字: k0811.c */
 2: #include <stdio.h>
 3: #include <string.h>
 4: 
 5: int main()
 6: {
 7:     int i;
 8:     char s1[] = "ABCDEF";
 9:     char s2[20];
10:     strcpy(s2,s1);
11:     i=strcmp(s1,s2);
12:     printf( "%s と %s を比較すると %d です。\n",s1,s2,i );
13:     strcat(s2,"A");
14:     i=strcmp(s1,s2);
15:     printf( "%s と %s を比較すると %d です。\n",s1,s2,i );
16: 
17:     strcpy(s1,"あ");
18:     strcpy(s2,"い");
19:     printf( "s1 の内容は %s 長さは %d\n",s1,strlen(s1) );
20:     printf( "s2 の内容は %s 長さは %d\n",s2,strlen(s2) );
21:     i=strcmp(s1,s2);
22:     printf( "%s と %s を比較すると %d です。\n",s1,s2,i );
23:     return 0;
24: }

~/c$ ./k0811
ABCDEF と ABCDEF を比較すると 0 です。
ABCDEF と ABCDEFA を比較すると -65 です。
s1 の内容は あ 長さは 3
s2 の内容は い 長さは 3
あ と い を比較すると -2 です。

「あ」の長さが3であることから、このシステムではUTF-8という文字コードを用いていることが分かります。OS,コンパイラの設計と設定など複数の条件が絡んでいます。

聖愛中学高等学校
http://www.seiai.ed.jp/
Dec. 2011