第23回
データ構造(2)~文字列という特殊な配列

Cの文字列

Cには『文字列型』というデータ型はなく、複数の文字の並びをchar型の配列として扱います。そのため、ちょっとした勘違いでとんでもない振る舞いをすることもあります。

最後に「0」を置いた特殊な配列

例えば"This is a pen."という文字列は、図1のような形でメモリに保存されます。char型は1バイトの数値ですから、配列の各要素には文字コードの数値が入ります(図1では16進数で表しています)。

最後の'\0'はエスケープシーケンスと呼ばれる制御文字で、数値の「0」を表しています。また、これはNULL文字とも呼ばれます ※1 。文字列の終端を、文字コードには使われない0('\0'という記号)で表しているのです。

配列はその要素数を明示してメモリに領域を確保できますが、常にすべての領域が文字で埋められるとは限りません。「Oh!」という3文字の場合もあれば「Oh! I think so.」といったように(スペースを含めて)15文字の場合もあるでしょう。

複数の商品に対する金額や複数の受験者の点数など数値を保持する配列であれば、予め規定された要素をすべて数値で満たすことが可能です。しかし文字列は、表す内容によって文字数──配列で使用する要素数──が異なります。そのため、終端を明確に示しておかなければなりません。その終端を示すための記号が、どのような場合にも文字として使用されない記号'\0'(数値の0=NULL)なのです。

このように、Cの文字列は「文字列としての終端に数値の0を配置した特殊な配列」なのです。

NULLは日本では一般に「ヌル」と呼ばれ、数値の0を表します。0にも型があり、文字列の終端に使われる'\0'はunsigned char型です。その他、ポインタを返す関数では処理に失敗したときにNULLポインタを返す仕様のものがありますが、その場合はアドレスのサイズによってunsigned short int型だったりunsigned long int型だったりします。ややこしいので、この段階では意識する必要はありません。回を追って紹介します

文字列と1文字の区別

Cでは、文字列とそれを構成する1文字とが異なる表現をされます。

Cでは文字列定数は" "(ダブルコーテーション)で囲み、1文字の定数は' '(シングルコーテーション)で囲みます。文字列型を持っている他の言語、例えばVisual Basicでは、文字が複数個あっても1個だけでもString型として扱い、定数(リテラル)とする場合は" "(ダブルコーテーション)で囲みます。

・Cの場合
文字列:"Hello!"
1文字:'H'

・VisualBasicの場合
文字列:"Hello!"
1文字:"H"

先述したように、Cの文字列は1文字が複数個並んだものでしかないため、配列としての文字列から1文字(配列の1要素)を取り出して処理できる――という特徴を持っています。

文字列の宣言

Cで文字列を変数として使用する場合、以下のように変数名に続けて[]内にその要素数を記述します。
char buffer[16];
一般の配列とまったく同じ扱い、というより一般の配列として宣言し、それを便宜的に『文字列』という形で扱う訳です。

上の例では、要素を16個持つbufferという名前のchar型配列を宣言しています。この場合、文字列"buffer"には15個(15バイト)の文字しか保持できません。最後の要素に終端を示すNULLが必要だからです。

Cの文字列では終端のNULLを保存する1バイトが必須なので、「保持させたい文字数+1」個の要素を指定します。例えば16文字保存できる文字列を宣言する場合には、
char buffer[17];
とすればよいのですが、これではパッと見たときに何文字保持できるかが分かりにくいため、
char buffer[16 + 1];
のように記述します。コンパイラを通せば、結果的にはbuffer[17]という形で処理されるのですが、ソースとしての分かりやすさ(文字16個と終端のNULL)を意識してわざと冗長に記述する訳です。

文字列の初期化

文字列変数 ※2 を宣言時に、文字列定数を直接設定することもできます。その場合は要素数を省略でき、設定した文字列の「文字数+1」個分の要素が自動的に確保されます。

char message[] = "Hello!";
この場合、配列messageの中身は図2のようになっています。

一方、先の例のように
char message[10];
と宣言した場合は、配列messageの各要素は初期化されていません。従って、何らかの処理によって文字列を設定しなければ、意味のある配列とはなりません。例えば、以下のように文字列をコピーする関数strcpyを使って、配列messgaeを初期化します。
strcpy(message, "Hello!");
この場合、コピーされる文字列"Hello!"は6文字で、終端のNULLを加えて7文字(7バイト)分の領域を埋めることになります。すると、配列messageの中身は図3のようになります。

配列に対して配列の要素数より少ない文字数の文字列をコピーした場合、終端のNULL以降は「空き」になっています。文字列としては、終端のNULL以降は認識されません。

実際には、Cには「文字列変数」という言葉はありません。本文で述べたように、char型配列を便宜的に文字列として扱うだけなので、言語の仕様から見ればあくまでもchar型の配列変数です