2.6 変数とデータ型
2.6.1 変数の定義と初期化
「情報処理」とは、情報をコンピュータに入力し、プログラムがこれを解析し、望まれる形に変換して出力するという基本的な過程を指します。これを行うには、プログラムが情報を受け取り、計算する必要があります。そのためにはまず、情報を記録する方法が必要になります。そうしなければ、受け取った情報や計算した結果を保存することができません。
そこで、プログラムは一時的な情報を主記憶装置に保存することで、演算結果などを記録します。主記憶装置は CPU とのデータ転送速度をシビアに考えて構成されている揮発性(電源を切ると保存していた情報を失う記憶装置)の装置で、ビット当たりのコストが比較的高価です。
ユーザーがデータファイルを保存している記憶装置は、ハードディスクやフロッピーディスクですが、これらの記憶装置は CPU へのデータ転送速度が極めて遅く、プログラムの一時的なデータ保存領域としては不向きです。その代わり、主記憶装置に比べビット当たりのコストが安価で、不揮発性(電力の供給が失われてもデータを保持できる記憶装置)なので長期のデータ保存に適しています。
主記憶装置に情報を保存するために、機械語では保存する場所を表すアドレスを指定します。しかし、そのような面倒なことはプロフェッショナルのプログラマでもやりたいと思う作業ではありません。計算が面倒ですし、わずかでも誤ったアドレスを指定すればプログラムはクラッシュしてしまいます。この作業は誰が見ても効率的と呼べるものではありません。
そこで、高水準プログラミング言語では変数を用います。変数とはメモリの特定の記憶領域の代名詞のようなものです。変数は識別子で区別され、コンパイラは識別子と物理的な記憶領域を関連付けることによって情報の書き込みや読み出しを正しく行います。これによって、私たちは複雑なメモリアドレスの計算から解放され、記憶領域の割り当てや計算をコンパイラに任せることができます。そのため、入門者は簡単にメモリを利用することができるようになります。
しかし、変数はメモリの記憶領域を表す識別子であり、実際の記憶領域にはサイズが存在します。ある記憶領域は4バイトを確保しているかもしれませんし、別の領域は1バイトかもしれないのです。このように割り当てる記憶領域のサイズを設定するために変数を宣言する必要があります。これには次のような構文を用います。
型 変数1 , 変数2 , ... ;
型には型指定子と呼ばれるものを指定します。型指定子は変数の種類を表すもので、コンパイラは型に基づいてメモリを割り当て、メモリ上のアドレスを計算します。
型の次のトークンには、変数の名前を指定します。変数の名前には英字と数字を使うことができますが、最初の文字には数字を使えず、必ず英字またはアンダーライン _ で始まらなければなりません。また、int や return といった、C 言語が使っているキーワードを識別子にすることもできません。識別子の大文字と小文字の違いは区別されます。この識別子の命名規則は、C 言語のあらゆる識別子の命名でこの束縛を受けます。
複数の変数を同時に宣言又は定義するには、カンマ , で区切ります。プログラムはここで指定した変数の名前で記憶領域にアクセスすることができます。最後に、宣言はセミコロン ; で終了します。構文上、宣言は文には含まれません。
型指定子は表1のようなものがあります。
型指定子 | サイズ |
---|---|
char | 1バイト |
int | CPUの標準整数サイズ。 |
float | 単精度浮動小数点数 |
double | float 型以上、long double 型以下の倍精度浮動小数点型 |
char 型は、サイズが決められているので極めてわかりやすいですね。これは、英数文字1文字分の記憶領域を表しています。int 型というのは通常の整数を格納するための記憶領域で、そのサイズはコンピュータによって異なります。このサイズは、例えば 32 ビット CPU であれば 32 ビット(4バイト)が割り当てられることになります。本書執筆の時点で、家庭用コンピュータの多くは Intel x86 互換の 32 ビット CPU が用いられていますが、64 ビット CPU のコンピュータであれば 64 ビット、古いCPUを用いている 16 ビットマシン(例えば Windows 3.1)であれば 16 ビットが割り当てられるということになります。
float や double は小数点数を扱う場合に用いる記憶領域です。誤差を可能な限り少なくしたい計算などに用いることができるでしょう。これらも int 同様に機種に依存します。型指定子が割り当てる具体的なサイズは、コンパイラのドキュメントを参照してください。次の文は、整数型の変数 iVariable1 と iVariable2 を定義しています。
int iVariable1 , iVariable2;
しかし、この時点では変数にどのような値が格納されているか定まっていません(何の意味もない不定値が格納されている)。変数に値を保存するには、代入演算子 = を用いて変数にデータを代入します。ただし、一般的な数学の記法と異なり、左辺に代入する変数を指定し、右辺に式を記述します。例えば次の文は、変数 iVariable1 に 10 という値を代入しています。
iVariable1 = 10;
これは、iVariable1 が示す記憶領域に 10 という値を保存することを意味しています。また、これとは別に変数の宣言時に初期値を与える方法もあります。変数の初期化には初期化子を用います。
型 変数1 = 初期式1 , 変数2 = 初期式2 , ...;
型指定子や変数名の指定までは先ほどの宣言と同様です。初期化子はその後に = を付け、変数の初期値を表す式を指定します。初期化されていない変数は、何らかの値が代入されるまで、どのような値を格納しているのか保証されません。確実に変数に初期値を与えたい場合はこのように初期値を与えるとよいでしょう。
#include <stdio.h> int main() { int iVariable1 = 10 , iVariable2; iVariable2 = 100; return 0; }
コード1は変数を宣言することによってメモリに指定された領域を確保して、その領域に指定された値を代入して終了します。変数の値を画面に表示する方法は、まだ説明していないのでこの場では行いません。そこで、このプログラムはエラーさえ出なければ成功としましょう。
このプログラムでは、まず変数の宣言を行なっています。int iVariable1 = 10 というのは iVariable1 という変数を 10 で初期化しています。その後、カンマを用いて iVariable2 を定義していますが、この変数は初期化されていません。続いて iVariable2 = 100 という代入式を用いて、変数 iVariable2 に値 100 を代入しています。
因みに、式における項のことをオペランドと呼びます。iVariable2 = 100 の場合 iVaeiable2 や 100 がオペランドで、= を代入演算子と呼びます。ただし初期化子と式を混同しないでください。変数初期化子に用いられる = は、通常の代入式とは基本的に異質のものです。
2.6.2 書式指定
さて、変数を宣言して値を代入する方法はわかりました。しかし、これでは本当に正しく値が代入されているのかどうかわかりません。そこで printf() 関数を使って変数の内容を出力したいと思います。
printf() 関数で文字列以外の値を出力するには、書式制御文字列というもので書式指定をしなければいけません。書式指定は、必ずパーセント記号 % で始まり、左から右に解釈されていきます。printf() 関数は単純にリテラル文字列を出力する関数ではなく、実は高度な書式変換関数で、変数を文字列中のどこに、どのような形で代入するかを指定することができます。それを行うのが書式制御です。
printf() 関数は第 2 引数以降、任意の数だけ引数を指定することができ、最初の第 1 引数には書式制御文字列を指定します。書式制御文字列には % 記号を用いた書式指定を用いることができ、printf() 関数は最初の書式指定を見つけると、第 2 引数の変数を指定した形で出力し、2 番目の書式指定を見つければ、第 3 引数の変数を指定した形で出力するというような仕組みになっています。そのため、書式制御文字列に存在する書式指定の数だけ、第 2 引数以降に変数を指定します。書式指定より引数の数が多いと、余分な引数は無視されますが、書式指定より引数の数が少ない場合、動作は保証されません。
printf() 関数の書式制御は、実際にはかなり複雑な仕組みになっているため、この場で詳しい説明はしません。今は変数の内容を表示するために必要な書式指定だけを説明します。
書式指定 | 引数型 | 変換結果 |
---|---|---|
%d 又は %i | int | 10進整数 |
%x | int | 符号なし16進数。9 以降は"abcdef"を使用 |
%X | int | 符号なし16進数。9 以降は"ABCDEF"を使用 |
%c | int | 文字 |
%s | char * | 文字列 |
%f | double | 浮動小数 [-]dddd.dddd 形式 |
%e | double | 浮動小数 [-]d.dddd e[+/-]ddd 形式 |
%E | double | 指数の前に付くのが e ではなく E である点を除いて、%e の書式と同じ |
%g | double | %f 又は %e のうち、指定値及び精度で表現可能な短い方の書式。 |
%G | double | 指数の前にあるのが e ではなく E である点を除いて、%g の書式と同じ |
%% | -- | 変換せずに % を表示する |
とりあえず、この場ではこれだけ理解していれば十分でしょう。int 型の変数を表示するには、書式指定に %d を指定し、その後の引数で目的の変数を指定します。例えば printf("%d" , iValiable); とすれば、iValiable 変数の値が表示されます。引数を複数指定する場合、引数の間はカンマ , で区切ります。
#include <stdio.h> int main() { int iValiable = 10; printf("iValiable = %d\n" , iValiable); return 0; }
このプログラムを実行すると iValiable = 10 と画面に表示されるはずです。この結果を見れば書式制御文字列で指定した %d が、iValiable の内容に変換されていることがわかります。
2.6.3 型指定子の混合
変数宣言子には、「変数の定義と初期化」で紹介した基本型以外にも、より細かく変数のサイズや性質を指定するための型指定子が存在します。先ほど紹介した基本的な型指定子のほかに、long と short という指定子も存在し、これらを用いることで通常の数値型とは異なる長さの変数を作成することができるようになります。これらの型指定子を用いて修飾した宣言には、次のようなものがあります。
型指定 | サイズ |
---|---|
short int |
16ビット以上、int 以下。int は省略可能。 |
long int | 32ビット以上、int と同じかそれ以上。int は省略可能。 |
long double | double と同じかそれ以上。 |
short int や long int は short や long というように省略した形を用いることが可能で、通常は省略します。一般に、short は 16 ビット、long は 32 ビットであることが多いでしょう。浮動小数点型のサイズも整数同様に処理系に依存するため long double は double と同じサイズであることもあれば、異なっていることもあります。例えば Microsoft Visual C++ 6.0 で long double は 80 ビット(符号に1ビット、指数に15ビット、仮数に64ビット)となっています。
また、数値型の変数は負数を表すことができます。しかし、負数を表現する場合は最上位ビットを符号用に用いるため、表現可能な最高値が半分になってしまうというデメリットもあります。負数を表現しない場合、最上位ビットも数値表現のために使うことができえれば、表現できる範囲が拡大します。
そこで、符号付の変数を宣言する場合は signed を、符号無しの場合は unsigned 型指定子を修飾します。一般的には signed を明記する必要はありませんが、コンパイラによってはオプションで変数のデフォルトを符号無しにするようなことも可能で、そのような場合に備えて負数を扱うことを明示的に表現するために用いることがあります。負数を扱う必要のない変数では unsigned を指定することで、高い数値を扱えることができるようになります。
例えば、符号付 char 型変数は -127 ~ +127 までの範囲を扱えます。1バイトは純粋に考えれば 255 まで、2進数の 1111 1111 まで扱えますが、符号付の場合はこのうちの最上位ビットがフラグとして使用されるため、符号付 char 型変数に 255 を代入すると、最上位ビットが 1 であるため負数と判断され、2 の補数表現では 2進数 1111 1111 の 2 の補数を求めた値 0000 0001 すなわち -1 となります。色などの情報を1バイトで扱う場合、負数を用いる必要はないので unsigned を指定して符号無しとして扱う方法が用いられます。
#include <stdio.h> int main() { signed char chVariable = 255; unsigned char uchVariable = 255; printf("chVariable = %d\nuchVaruabke = %d\n" , chVariable , uchVariable); return 0; }
コード3は、符号付と符号無しの char 型変数に 255 を代入しています。計算どおり、符号付変数は -1 を、符号無し変数は 255 を出力します。では、1バイトの変数に対して1バイトでは表現できない高い数値を、すなわち 255 以上の値を代入するとどうなるのでしょう?C 言語では、変数のサイズ以上の値を代入すると、符号無しの場合は上位ビットがビットが切り捨てられて代入されます。符号付の場合は実装に依存します。