10. 文字と文字列
 実用的なプログラムは,

 10.1 文字 (caharacter)と文字型変数

 これまで,殆ど数値データのみを扱ってきました.しかし,実際に実用的なアプリケーションを作ってみればわると思いますが,多くのアプリでは,随所で文字処理が頻繁に実行されます.多分,インターネットのブラウザなどは,そのコードの多くが文字処理部分で占められると思います.
 ここでは,C言語における文字データの取扱いを紹介します.配列とポインターさえきちんと理解していれば,簡単に理解できます.

 まずは,文字(ほんとは1バイトの数値なのですが)は,英語でcharacterと呼ぶと言うことだけを覚えておいて,次のプログラムの理解を試みてください.鋭い人は,これだけで十分かも知れません.


 プログラム例 10.1.1  実行結果
#include <stdio.h>
void main(void){
    char x, y, z;
  
    x = 'a';
    y = 'b';
    z = 'c';

    printf("%c %c %c\n", x, y, z);
}
 a b c


 上のプログラムを見ればわかるように,文字データも変数に代入して扱うことができます.
文字データを記憶する変数は文字型変数と呼ばれ,メモリー中では文字1個につき1バイトのスペースが割り当てられます.(ただし,日本語文字(全角文字)の場合は2バイトです.ここでは,半角文字,asciiコードを扱います.)
もちろん,文字型変数を使うときは,他の数値型の変数と同様,最初に変数宣言を必要とします.文字型変数の宣言文は,次のように書きます.
    char x ;
 文字型変数に文字定数を代入するには,次のように書きます.
    x = 'a' ;
ここで,記号シングルクオーツ(' ')で囲まれた文字は,データとしての文字(文字定数)を意味します.ただし,文字型変数1個には1個の文字しか代入できません.
また,文字型変数に記憶された文字は,printf()関数を使ってディスプレィ上に表示することができますが,数値型データの場合と同様,書式指定子が必要となります.この場合の書式指定子は,%cです.


 通常,文字データは,単語や文章単位で扱うことが多いので,文字型の配列変数を使って,単語や文章を扱います.次のプログラムでそれを示します.しかし,このプログラムは文字データの処理を理解してもらうためのもので,実際にはこのように書くことはありません.(もちろん,そのような書き方をしてもかまいませんが)

 プログラム例 10.1.2  実行結果
#include <stdio.h>
void main(void){
    int nchar = 4;
    char x[4] = {'b', 'o', 'o', 'k'};
    int i;

    for(i = 0; i < nchar ; i++){
        printf("%c", x[i]);
    }  
}
 book



 10.2 文字と文字列
 C言語では,単語や文章などのひとまとまりの文字型データを文字列と呼びます(この表現は正確ではないですが).前節では,そのようなデータを配列に記憶させて扱うことができると説明しました.しかし,プログラム例10.1.2のような書き方をだと,プリント処理を書くだけでも面倒です.そこで,もっと簡単に扱える工夫が必要です.以下では,その方法を説明します.ちょっと回りくどい説明ですが,我慢して読んでみてください.

 文字列を配列に記憶させて扱うときは,文字列の文字数より1個だけ余分に配列要素を準備し,そこへ文字列の終了の目印を入れます.実際には,このような目印としてヌル記号とよばれる'\0'を使います.2文字で書かれていますが,改行記号の'\n'と同様,実際には1バイトの文字です.このように\記号と組み合わせて表現される特別な記号をエスケープシーケンスとよびます

プログラム例10.1.2では,文字列をプリントするためにあらかじめ文字の数(配列のサイズ)を知っておく必要がありましたが, 文字列の最後に\0を追加することで,たとえば,プリント時に文字列を構成する文字の個数がわからなくても,その記号を目印にちゃんと処理ができるようになります.
以下のプログラム例10.2.1と10.2.2は,そのことを説明するためのものです.プログラムをよく眺めてみてください.

 プログラム例 10.2.1  実行結果
#include <stdio.h>
void main(void){
    char x[5] = {'b','o','o','k','\0'};
    char y[7] = {'s','c','h','o','o','l','\0'};
    int i;

    i=0;
    while(x[i] != '\0'){
        printf("%c", x[i]);
        i++;
    }  
    printf("\n");

    i=0;
    while(y[i] != '\0'){
        printf("%c", y[i]);
        i++;
    }  
    printf("\n");
}
book
school


 下記の2つのプログラムは,「\0はwhile()やif()文中の条件式としては数値の0と同じと評価される」ことを利用して,上のプログラムを書き換えたものです.条件文中での0の評価については「演算子と結果」のところで説明しました.
 注意すべき点は,文字列の最後に\0を追加することで,文字数を知らなくとも文字列の終端がわかるということです.

 プログラム例 10.2.2  実行結果
#include <stdio.h>
void main(void){
    char x[5] = {'b','o','o','k','\0'};
    char y[7] = {'s','c','h','o','o','l','\0'};
    int i;

    i=0;
    while(x[i]){
        printf("%c", x[i]);
        i++;
    }  
    printf("\n");

    i=0;
    while(y[i]){
        printf("%c", y[i]);
        i++;
    }  
    printf("\n");
}
book
school


次のプログラム例は,配列要素の指定にポインター変数を使った例です.上のプログラムと全く同じですね.while()文の条件式の表現に注意してください.文字型変数のサイズは1バイトですから,char * 型のポインター,ptrはptr++でアドレスの値も配列要素も1だけ進むことになります.もちろん,ポインター変数ptrが\0を指すとき,*ptrは\0ですから,while()ループは終了ということになります.

 プログラム例 10.2.3  実行結果
#include <stdio.h>
void main(void){
    char x[5] = {'b','o','o','k','\0'};
    char y[7] = {'s','c','h','o','o','l','\0'};
    char *ptr;

    ptr=&x[0];
    while(*ptr){
        printf("%c", *ptr);
        ptr++;
    }  
    printf("\n");

    ptr=&y[0];
    while(*ptr){
        printf("%c", *ptr);
        ptr++;
    }  
    printf("\n");
}
book
school



でも,実際にはこのようなプログラムを書くことはありません.
ごちゃごちゃといろいろ書きましたが,ここで説明したことは,一度読んだら,忘れてもらって結構です.



 10.3 あらためて文字列の扱い方

 文字データを配列変数に代入す方法として,つぎの3とおりの方法が考えられます.
    char x[4] = {'b','o','o','k'};      /* 方法1 */
    char x[5] = {'b','o','o','k','\0'};    /*  方法2 */
    char x[5] = {"book"};       /*  方法3 */
 方法1は,x[0]からx[3]までの要素に1個づつ半角文字を代入していきます.
 方法2は,x[0]からx[3]までの要素に1個づつ半角文字を代入し,要素[4]に\0を代入します.
 方法3は,x[0]からx[3]までの要素に""で囲んだ文字列をまとめて代入します.ただし,この時,要素x[4]には,自動的に\0が代入されます.文字列とは,方法2,3のように文字データの終端が\0で終わる一連の文字データのことをいいます.方法1で作成された配列は,単なる文字データの配列です.

文字列データをプリントするときは,printf()関数に配列の先頭要素のアドレスを渡します.そして,書式指定子には%sを使います.
 先に「ポインター変数」で説明したように配列の先頭アドレスをprintf()関数に渡すときの表現には3つあります.以下の3つのプログラムは同一の処理を実行するプログラムをこれら3つの書き方で表現したものです.



最初の書き方の例は,&演算子を0番目の配列要素に作用させて,アドレスを求める方法です.

 プログラム例 10.3.1  実行結果
#include <stdio.h>
void main(void){
    char x[5] = {"book"};
    char y[7] = {"school"};

    printf("%s\n", &x[0]);
    printf("%s\n", &y[0]);
}
book
school


配列変数名は先頭アドレスを返すので次のように書いても同じことです.


 プログラム例 10.3.2  実行結果
#include <stdio.h>
void main(void){
    char x[5] = {"book"};
    char y[7] = {"school"};

    printf("%s\n", x);
    printf("%s\n", y);
}
book
school



もちろん,配列の先頭アドレスをポインター変数に記憶させてprintf()関数に渡すこともできます.


 プログラム例 10.3.3  実行結果
#include <stdio.h>
void main(void){
    char x[5] = {"book"};
    char y[7] = {"school"};
    char *ptr;

    ptr = x;
    printf("%s\n", ptr);
    ptr = y;
    printf("%s\n", ptr);
}
book
school




 10.4 実は文字も1バイトの整数値なのです

 前に文字も1バイトの数値だと説明しましたが,キー入力する文字などは,整数型の数値として下表のように定義されています.これを ASCIIコードと呼ぶことは前にも説明したとおりです.

 表中,青色の部分がディスプレィやプリンターなどで表示される文字で,淡青色の部分は制御文字とよばれ,通信や制御などのプログラムで使用される特別なの意味を持った文字です.制御文字のうち,青色の網掛け部分の文字は,文字表示の時にもよく使われる特殊な文字です.覚えておくとプログラミングに役立ちます.
また,表中の【spc】はスペースの事で,NULLは\0のことです.\0が0同等とみなされると説明したのが理解できたと思います.

   ASCII コード表
0NULL16DLE
1SOH17DC1
2STX18DC2
3ETX19DC3
4EOT20DC4
5ENQ21NAK
6ACK22SYN
7BEL23ETB
8BS24CAN
9HT25EM
10LF26SUB
11VT27ESC
12FF28FS
13CR29GS
14SO30RS
15SI31US
32SP480
33!491
34"502
35#513
36$524
37%535
38&546
39'557
40(568
41)579
42*58:
43+59;
44,60<
45-61=
46.62>
47/63?
64@80P
65A81Q
66B82R
67C83S
68D84T
69E85U
70F86V
71G87W
72H88X
73I89Y
74J90Z
75K91[
76L92\
77M93]
78N94^
79O95_
96`112p
97a113q
98b114r
99c115s
100d116t
101e117u
102f118v
103g119w
104h120x
105i121y
106j122z
107k123{
108l124|
109m125}
110n126~
111o127DEL


 次のプログラムは,変数CODEに数値としてのコードを代入し,それを書式指定子%dと%cの両方で表示させるためのものです.
文字は,%dで表示させれば数値扱い,%cで表示させれば文字として表示されます.上の表と比べてみてください.


 プログラム例 10.4.1  実行結果
#include <stdio.h>
void main(void){
    int  code;

    printf("ascii code :character\n"); 
    for(code = 32; code <= 126; code++)
    {
        printf("%3d:%c  ", code, code); 
        if(!(code % 6))printf("\n");
    }
}
ascii code :character
 32:    33:!   34:"   35:#   36:$
 37:%   38:&   39:'   40:(   41:)   42:*
 43:+   44:,   45:-   46:.   47:/   48:0
 49:1   50:2   51:3   52:4   53:5   54:6
 55:7   56:8   57:9   58::   59:;   60:<
 61:=   62:>   63:?   64:@   65:A   66:B
 67:C   68:D   69:E   70:F   71:G   72:H
 73:I   74:J   75:K   76:L   77:M   78:N
 79:O   80:P   81:Q   82:R   83:S   84:T
 85:U   86:V   87:W   88:X   89:Y   90:Z
 91:[   92:\   93:]   94:^   95:_   96:`
 97:a   98:b   99:c  100:d  101:e  102:f
103:g  104:h  105:i  106:j  107:k  108:l
109:m  110:n  111:o  112:p  113:q  114:r
115:s  116:t  117:u  118:v  119:w  120:x
121:y  122:z  123:{  124:|  125:}  126:~
Press any key to continue


もちろん,つぎのようなプログラムだって書くことができます.これも,文字が数値であることの証明ですね.
文字をデータとして与えるときは(’’)で囲むことを忘れないようにしましょう.もし,それを忘れたら変数名になってしまいます.

 プログラム例 10.4.2  実行結果
#include <stdio.h>
void main(void){
  int  code;

   printf("ascii code :character\n"); 
   for(code = 'A'; code <= 'X' ; code++){
        printf("%3d:%c  ", code, code); 
        if(!(code % 6))printf("\n");
   }
}
ascii code :character
 65:A   66:B
 67:C   68:D   69:E   70:F   71:G   72:H
 73:I   74:J   75:K   76:L   77:M   78:N
 79:O   80:P   81:Q   82:R   83:S   84:T
 85:U   86:V   87:W   88:X



 10.5 制御文字の機能

asciiコードの制御コードのうち,BS,HT,LF,CRについて,その機能を見てみましょう.これらの制御コードは,printf()文などの出力文と組み合わせて用いるときに効果を発揮します.もちろん,書式%c(文字扱い)の時のみ有効で,書式指定子が%d(数値扱い)のときは制御コードとしては機能しません.

プログラムを見てみましょう.
 1)BSは,キーボードの【BS】キーと同じ機能を持ちます.カーソルを1文字分戻します
   ”012345”をプリントした後,文字1個分戻って,”ABCDE”をプリントします.
 2)HTは,キーボードの【TAB】キーと同じ機能を持ちます.
   ”012345”をプリントした後,スペースを空けて,”ABCDE”をプリントします.
 3)CRは,カーソルを行の先頭へ戻します.
  ”012345”をプリントした後,CRでカーソルが行の先頭へ戻って”ABCDE”をプリントします.
 4,5)LFとCR+LFは,改行する機能を持ちます.

プログラムの実行結果を丁寧に見てください.

 プログラム例 10.5.1  実行結果
#include <stdio.h>
#define    BS      8  
#define    HT      9  
#define    LF     10  
#define    CR     13  

void main(void){
  char string0[7]={"012345"};/*文字列*/
  char string1[6]={"ABCDE"};

  printf("0)制御コードなし\n");
  printf("%s", string0); 
  printf("%s", string1);
  printf("\n\n");
 
  printf("1)制御コード BS の効果\n");
  printf("%s",string0);
  printf("%c", BS);
  printf("%s", string1);
  printf("\n\n");
 
  printf("2)制御コード HT の効果\n");
  printf("%s",string0);
  printf("%c", HT);
  printf("%s", string1);
  printf("\n\n");
 
  printf("3)制御コード CR の効果\n");
  printf("%s",string0);
  printf("%c", CR);
  printf("%s", string1);
  printf("\n\n");
 
  printf("4)制御コード LF の効果\n");
  printf("%s",string0);
  printf("%c", LF);
  printf("%s", string1);
  printf("\n\n");

  printf("5)制御コード CR+LF の効果\n");
  printf("%s",string0);
  printf("%c%c", CR, LF);
  printf("%s", string1);
  printf("\n\n");
}
0)制御コードなし
012345ABCDE

1)制御コード BS の効果
01234ABCDE

2)制御コード HT の効果
012345  ABCDE

3)制御コード CR の効果
ABCDE5

4)制御コード LF の効果
012345
ABCDE

5)制御コード CR+LF の効果
012345
ABCDE





 10.6 エスケープシーケンス

 実は,制御コードの一部は,姿を変えてC言語で登場します.
これまで使ってきた次のようなエスケープシーケンスとよばれる一連の記号がそうなのです.
  '\n', '\b', '\t','\n','\0'
では,これらの記号がどの制御記号に対応するか,プログラムを使ってみてみましょう. そうです.エスケープシーケンスを%dで表示させて,先のASCIIコードの表と対応を調べてみましょう.
結果は,次の通りです.エスケープシーケンスと制御記号の対応は,asciiコード表を使って自分で調べてみてください
このような知識は,実用的なプログラムを作成するとき,役に立ちます.頭の片隅にメモっておいて下さい.

注意:\記号をプリントするときは\\と書きます.

 プログラム例 10.6.1  実行結果
#include <stdio.h>
void main(void){
    printf("エスケープシーケンスのコード\n");
    printf("1)\\b(バックスペース):%d\n", '\b');
    printf("2)\\t(タブ):%d\n", '\t');
    printf("3)\\r(キャリッジリターン):%d\n", '\r');
    printf("4)\\n(改行):%d\n", '\n');
    printf("5)\\0(ヌル文字):%d\n", '\0');
}
エスケープシーケンスのコード
1)\b(バックスペース):8
2)\t(タブ):9
3)\r(キャリッジリターン):13
4)\n(改行):10
5)\0(ヌル文字):0