文字の正体は整数値

こんにちは、めのんです!

今回は文字について解説することにします。
一般的な意味での「文字」は、その形状であるとか、それが表す音や意味のことになると思います。
けれどもコンピュータで扱う文字というと、そういったこと(フォントなど)もあるにはありますが、主には文字集合や符号化の話になりますね。

文字集合

文字集合に関していえば、Cには「基本文字集合」「拡張文字集合」という概念があります。
このうち、基本文字集合は処理系によらず必ず対応していますが、拡張文字集合は処理系やロケールによってまちまちです(ロケールはPHPにもあるので大丈夫ですよね?)。
Cの基本文字集合はほぼASCIIの範囲だと思って構いません。
ただし、厳密なことをいえば「$」や「@」は含まれていません。

ソース文字集合と実行文字集合

Cで扱う文字集合には基本文字集合と拡張文字集合があることをお話しました。
文字集合にはもうひとつ別の角度から見た分類のしかたがあります。
それが「ソース文字集合」「実行文字集合」です。

ソース文字集合は文字通りソースコードで使用する文字集合のことです。
ソース文字集合の中に「基本ソース文字集合」「拡張ソース文字集合」があります。

実行文字集合は実行時に使用する文字集合のことです。
実行文字集合の中にも「基本実行文字集合」「拡張実行文字集合」があります。

このようにソース文字集合と実行文字集合という別の概念があることからわかるように、ソースコードで使う文字集合と実行時に使う文字集合は必ずしも一致しません。
たとえば、ソースコードはUnicodeを使って書くけれど、実行時はシフトJISを使う、あるいはその逆とといったことがあります。
GCCでもコンパイルオプションを使ってそれらを個別に指定することができます(これについてはまた別の機会に解説できたらと思います)。

あまり最初から欲張ってもいけませんので、まずは基本ソース文字集合と基本実行文字集合だけを使って解説を進めていきたいと思います。
とはいえ、ASCIIに含まれる「$」や「@」ぐらいなら普通に使っても大丈夫だと思いますよ。

文字型

Cのプログラムで文字を表現するには整数値を用います。
この整数値は文字の形状を表しているのでもなければ、音や意味を表しているのでもありません。
実際の文字のどれを指すかを表す単なる番号だと考えてください。
PHPでもord関数を使えば文字列の先頭バイトの数値を調べることができますが、その数値のことだと考えるといいでしょう。

もうひとつ注意しないといけないのは、Cでは文字列と文字はまったく別の概念だということです。
PHPでは文字列と単一の文字を特に区別することなく文字列として扱っていましたね。
Cでは文字列と文字は別物で、読んで字のごとく文字の列が文字列なんです(文字列については次回解説します)。

1バイトの文字型

Cには文字を表すための型があって「文字型」といいます。
「文字型」といっても実は単なる整数型のなかまで、次の3つがあります。

  • char
  • signed char
  • unsigned char

どの型には「char」が含まれますが、これは「character」の意味です。
このうち「signed char」型は符号付き、「unsigned char型」は符号無しの整数型です。
「char」型が符号付きか符号無しかは処理系定義になっています。
このう文字を扱う上で一番よく使うのがchar型ですが、符号の有無は決まっていないので注意が必要になります。

先ほど紹介した3つの文字型のサイズはどれも1バイトになります。
ただし、Cでいう1バイトというのは8ビットとはかぎりません。
8ビット以上であればCの規格上は何ビットでもかまわないことになっています。
とはいえ、普段使う処理系のほとんどは1バイトは8ビットですし、GCCももちろん8ビットです。

その他の文字を表す型

実はCには先ほどの3つ以外にも文字を表すための型があります。
C89からC99まではwchar_t型というワイド文字を扱う型があります。
C11以降はそれに加えて、char16_t型とchar32_t型という主にUTF-16とUTF-32を扱うための型があります。

これらの文字型については今回は紹介だけにとどめることにします。
詳細はまた別の機会に紹介できたらと考えています。

文字型オブジェクトと文字定数

前振りが随分長くなってしまいましたが、これから実際のプログラミングに進むことにしましょう。

文字型オブジェクトの宣言もint型などと同じように行うことができます。
たとえばこんな感じです。

char a;                 // 初期化子無し
signed char b   = 123;  // 整数定数を使った初期化子
unsigned char c = 'a';  // 文字定数を使った初期化子

整数定数を使った初期化子についてはint型の場合と同じですので説明はとくにいらないでしょう。
3行では「文字定数」を使って初期化子を書いていますね。

Cでは文字定数はシングルクォーテーションで文字を囲むことで表現します。
PHPではシングルクォーテーションでもダブルクォーテーションでも文字列を表しましたが、Cではシングルクォーテーションは単一の文字を表します。

ちなみにダブルクォーテーションで囲めば(たとえ1文字しかなくても)文字列定数になります。
詳しい解説はしませんでしたが、これまでも”hello, world!”のように文字列定数を使ってきましたね。

ところで、非常にややこしい話なんですが、先ほどの例に登場した ‘a’ のような文字定数はchar型でもsigned char型でもunsigned char型でもなくint型です。
なぜそんなことになっているのかは、このあとすぐにわかってくると思います。
なお、int型の文字定数であっても、とくに変換などしなくても文字型の初期化子に使えていますし代入もできます。
Cはそのあたりがわりと緩い方だと思います。

文字の入出力

文字が使えるようになりましたので、今度は文字を標準入力や標準出力を使って入出力してみることにしましょう。

文字の出力

文字を標準出力に書き出すには「putchar」関数を使います。
具体例を見ていきましょう。

#include <stdio.h>

int main(void)
{
  char c = 'a';
  putchar(c);
  putchar('b');
  putchar('c');
  return 0;
}

このプログラムをコンパイルして実行すれば、「abc」という文字列が出力されます。
ぜひ実際に試してみてください。

putchar関数の引数はint型の値を受け取ります。
けれども、その値は0からunsigned char型の最大値の範囲(普通は0~255)でなければなりません。
そしてうまく出力ができれば引数と同じ値を返します。
もし何らかの事情で失敗すればマイナスの値を返します。

char型は先ほども解説しましたが符号付きか符号無しかは処理系定義です。
もしchar型のオブジェクトにマイナスの値が入っていると、それはルール違反なので何が起きてもおかしくない状況になります(「未定義の動作」といいます)。

基本実行文字集合の範囲しか使わなければ文字定数がマイナスの値になることはありませんが、拡張実行文字集合を使ったりエスケープシーケンスを使ったりするとマイナスの値になることもあります。
エスケープシーケンスはPHPにあるように ‘\n’ とか ‘\x80’ のような書き方のことです。
‘\x80’ とか ‘\200’ とかだとマイナスの値になってしまいますね。
こういうこともあって文字定数の型はint型になっています。

putchar関数のほかに、printf関数を使って文字を出力することもできます。
たとえばこんな感じです。

char c = 'x';
printf("%c\n", c);

いつものmain関数の枠組みや#includeは省略しましたが、ちゃんとしたプログラムにするときは省略せずに書いてくださいね。
printf関数で「%c」を使えば、それは文字型の出力であることを意味しています。

それではちょっとひねって、int型を出力するときに使った「%d」を使ってみましょう。

char c = 'x';
printf("%d\n", c);

なんて出力されましたか?
それが文字 ‘x’ に相当する整数値になります。
このように、Cでは文字は単なる整数値に過ぎません。
大事なポイントなのでしっかり覚えておきましょう。

文字の入力

今度は標準入力から文字を読み込んでみましょう。
標準入力から文字を読み込むには「getchar」関数を使います。

#include <stdio.h>

int main(void)
{
  char c;
  c = getchar();
  putchar(c);
  return 0;
}

上のソースコードをコンパイルして実行してみてください。
文字の入力待ち状態になりますので、何でもいいので1文字入力してEnterを押しましょう。
先ほど入力した文字がオウム返しに標準出力に書き込まれたはずです。

getchar関数は標準入力から1文字読み込んでその文字を返します。
もしなんらかの事情で失敗したときはマイナスの値を返します。

今回はじめて代入が出てきましたね。
char型のオブジェクトcにgetchar関数が返した値を代入しています。
代入の書き方はPHPと同じで等号を使いますが、とくに説明はいらないでしょう。

それでは今回の解説は以上になります。
今回解説した文字と前回解説した配列を踏まえて、次回は文字列の解説をしたいと考えています。
どうぞご期待ください!