文字は1つの文字、文字列は文字がいくつか並んだものをいいます。
C言語の古い仕様では英字(A-Zの大文字小文字)と数字、若干の記号だけが文字です。つまり文字は1バイトで、2バイト以上は文字列になります。
最近は国際化が進みユニコードを使って日本語でも中国語でも文字を同様の考え方で認識できるようになってきましたがこの仕様はC言語の仕様の中に色濃く残っています。
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の範囲でも改行とかタブといった制御文字がありますから画面に表示するときには注意が必要です。
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: }
実行結果はこうなります。
~/c$ ./k0801 A A の方が a より小さい
k0801.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: }
char moji2 = 'a'
と書くつもりが、シングルクォートを忘れてchar moji2 = a
と書いてしまいました。間違えていますが、コンパイルは通ります。
実行結果は次のようになります。
~/c$ ./k0802 A A の方が x より小さい
moji2
の値が 'x'
になっています。 'a'
は文字ですが、 a
は変数名と判断され、今回はたまたま a
という変数があったのでエラーになりませんでした。
その上 char は実体が整数なので、a
の値である120の代入が正常にできてしまいます。
そして120を文字コードとして解釈すると 'x'
になったということです。
40+1 =41 が A の文字コードです(この数値は16進数)。
つまり左にある16進数と上の16進数を加えたものが交点の文字コードです。
C言語では 0x41 と0xを前に置くことで16進数を直接プログラム中に書くことができます。
制御文字は相手により設計により機能が異なりますので全部が有効ではありません。
+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 |
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
1バイト文字しか使えないcharが活躍する場面はキー入力を受け取るときでしょう。
「A ですか B ですか 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: }
scanf("%c", &a);
printf("%c",a);
で文字を画面に出したのと同じような仕組みでcharをキーボードから受け取ります。キーボードからの入力がなければ待ちます。実行結果は
~/c$ ./k0804 A ですか B ですか C ですか B B にしました。
Bはキー入力の時に同時に画面に表示されるもので、エコーバックといいます。文字列の入力の時にどこまで入力したかわからなくならないようにしています。また、[Enter]キーを押すまでは[Backspace]キーで修正することができます。
A,B 以外では C になることも確認してください。
~/c$ ./k0804 A ですか B ですか C ですか a C にしました。
課題8-4 を改良して小文字でも受け付けるようにします。
[ 考えてください ]の部分は 9: を参考に書き換えてください。
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: }
||
は「または」を示す論理演算子でした。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 にしました。
AB以外のキー入力が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: }
&&
は「かつ」を示す論理演算子でした。whileで繰り返しで説明しています。
優先順位により == の比較演算子のほうが先に判断されますが、自信がなければ括弧をつければよいでしょう。( abc != 'A') && (abc != 'B') && (abc != 'C')
実行結果は
~/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を終えているのです。
キーボードから1文字ずつ受け取って表示し、さらにキー入力を待つプログラムです。
小文字の z を入力すると終了します。
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 );
文字列を文字の配列に入れてそのまま表示するのは次のプログラムの内容だけ知っていれば用が足ります。
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
配列として個々の文字を取り出したみたのが11:からの部分です。
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
文字列の関数と必要なinclude文です。
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 です。
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,コンパイラの設計と設定など複数の条件が絡んでいます。
聖愛中学高等学校