式内部での型変換

C では1つの式の中に異なる型を混在させることができる。これは、式中に異なる方が現れた時に、どのように解消するかが決められているから。

整数拡張

式中に char や short int が使われていると、その式が評価されるときに必ず int に拡張される。ただし、この拡張は式の評価時だけの一時的なものであって、変数自体が拡張されるわけではないので注意。

型の昇格

整数拡張のあと、コンパイラはすべてのオペランドを一番大きなオペランドに合わせて変換する。これを型の昇格という。昇格の規則は次の通り。上から順に見ていって、当てはまる変換が行われる。

一方のオペランドの型もう一方のオペランドが昇格する型
long doublelong double
doubledouble
floatfloat
unsigned longunsigned long
longlong
unsignedunsigned

要するに、大きな方のオペランドの型にもう一方も変換されるってことだ。

1つ、特殊なケースがある。一方が long、もう一方が unsigned int で、しかもその unsigned int の値を long で表せない場合、両方が unsigned long に変換される。

次のプログラムは、int と float の演算をしているけど、変数 i が float に変換されて、出力は float になる。

#include

int main(void)
{
    int i;
    float f;

    i = 10;
    f = 23.25;

    printf("%f\n", i * f);

    return 0;
}
takatoh@nightschool $ ./sample_4_5a
232.500000

もう1つ。型変換の規則は演算のひとつひとつについて適用されるので、次のプログラムでは、(10 / 3) は整数であり、100.0 を割るときに初めて float に変換されるので、答えは 33.3333… になる(3.0 にはならない)。

#include

int main(void)
{
    printf("%f\n", 100.0 / (10 / 3));

    return 0l;
}
takatoh@nightschool $ ./sample_4_5b
33.333333

変数の初期化

変数を宣言するときに、ついでに初期化することができる。一般的な形式は次の通り。

型 変数名 = 値;

グローバル変数の初期化に使えるのは定数だけ、ローカル変数の初期化には定数、変数、関数呼び出し(戻り値)が使える。
グローバル変数は最初に1回だけ初期化され、ローカル変数は関数が呼び出されるたびに初期化される。
明示的に初期化されないグローバル変数は 0 に設定されるけど、ローカル変数の場合は値が不定になる。

変数をまとめて宣言するとき、初期化する変数としない変数を混ぜることができる。

int min = 0, count, max = 100;

次のプログラムでは、main 関数の中のローカル変数 y をグローバル変数 x で、ローカル変数 z を関数の戻り値で初期化している。

#include

int x = 10; /* global */

int myfunc(int i);

int main(void)
{
    int y = x; /* initialize with global variable */

    int z = myfunc(y); /* initialize with function */

    printf("%d %d\n", y, z);

    return 0;
}

int myfunc(int i)
{
    return i / 2;
}
takatoh@nightschool $ ./sample_4_4
10 5

定数

ここでいう定数は、名前のついた定数、例えば Ruby の FOO とかのことじゃなくて、いわゆるリテラルのこと。

  • 整数の定数は小数点と小数部がない数値。例:10、-100
  • 浮動小数点数の定数は、小数点と小数部がついた数値。例:10.0、-2.5
  • 浮動小数点数の定数は、指数表記もできる。例:1.25E+03
  • 文字定数は、引用符(’ ‘)で囲む。例:’a’
  • 文字列定数は、二重引用符(” “)で囲む。例:”hogehoge”

ここでちょっと注意点。C には文字列というデータ型はない。文字列は文字の配列として扱われている。これはまた後で出てくるはず。

コンパイラが数値の定数を解釈するとき、デフォルトでは互換性のあるデータ型のうちその数値がおさまる最小の型だと解釈する。ただし、浮動小数点数は double と解釈する。これを変えたいときは次のような接尾辞をつける。

  • 浮動小数点数:F をつけると float、L をつけると long double
  • 整数:U をつけると unsigned、L をつけると long int

数値を変数に代入するときには、その変数の型に解釈してくれるのであまり気にすることはないけど、関数の引数として直接使うときには注意が必要。次のプログラムは、「42340」ではなく「-23196」と出力してしまう。

#include

int main(void)
{
    printf("%hd\n", 42340);

    return 0;
}
takatoh@nightschool $ ./sample_4_3
-23196

これは、%hd という指定子から引数の数値が short int だと解釈してしまうから。正しく出力するには、%hu 指定子(符号なし short int)を使う必要がある。

変数の宣言場所

変数を宣言する場所と変数の有効範囲は次の通り。

  • 関数の外で宣言するとグローバル変数
  • 関数の中で宣言するとその関数だけのローカル変数
  • コードブロックの中で宣言するとそのブロックだけのローカル変数

また、次の制約がある。

  • 変数は使うより前に宣言しなければいけない

関数の外だとグローバル、中だとローカルってのはいいとして、コードブロックのローカル変数ってのは使いどころがよくわからないな。でも本には「実際には、ほとんどのCプログラマは1つの関数で使用するすべての変数をその関数のブロックの最初に宣言します。」って書いてあるから、そうしておけば良さそう。

グローバル変数とローカル変数は同じ名前でも構わない。有効な範囲にローカル変数があればそれが使われる。なければグローバル変数が使われる。
次のプログラムには、同じ count という名前のグローバル変数とローカル変数が使われているけど、それぞれ独立している。

#include

void f1(void);

int count; /* global */

int main(void)
{
    count = 10;
    f1();
    printf("value of count in main(): %d\n", count);

    return 0;
}

void f1(void)
{
    int count; /* local */

    count = 1000;
    printf("value of count in f1(): %d\n", count);
}
takatoh@nightschool $ ./sample_4_2
value of count in f1(): 1000
value of count in main(): 10

データ型修飾子とデータのサイズ

void 以外の基本データ型には修飾子をつけることによって、よりきめ細かく指定することができる。データ型修飾子には次の4つがある。

  • long
  • short
  • signed
  • unsigned

long と short はそれぞれデータサイズが変わる。といっても ANSI の規定が厳密じゃないみたいなので、処理系によって違うらしい。signed と unsigned は符号つきか符号なしかの違い。省略すると符号つきと解釈される。
データ型修飾子はそれぞれ適用できるデータ型が決まっているので、表にまとめてみた。

shortint
longint, double
signedchar, int
unsignedchar, int

それぞれのデータサイズは次のプログラムで確認してみる(ググって調べた)。

#include

int main(void)
{
    printf("char = %lu\n", sizeof(char));
    printf("int = %lu\n", sizeof(int));
    printf("short int = %lu\n", sizeof(short int));
    printf("long int = %lu\n", sizeof(long int));
    printf("float = %lu\n", sizeof(float));
    printf("double = %lu\n", sizeof(double));
    printf("long double = %lu\n", sizeof(long double));

    return 0;
}
takatoh@nightschool $ gcc --version
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

takatoh@nightschool $ ./sample_4_1
char = 1
int = 4
short int = 2
long int = 8
float = 4
double = 8
long double = 16

単位はバイト。