定数と型変換

定数は数学の円周率のようなものです——一度決めたら変わりません。型変換は両替のようなものです:異なる通貨には固定のレートがありますが、変換時の精度の低下には注意が必要です。

リテラル

リテラルはコードに直接書く固定値です。計算の必要がなく、一目で値がわかります。

整数リテラル

C
int a = 42;
int b = 0xFF;
int c = 052;
int d = 0b101010;
基数 プレフィクス 10 進数の値
10 進 なし 42 42
16 進 0x または 0X 0xFF 255
8 進 0 052 42
2 進(C99 GNU 拡張 / C23 規格) 0b または 0B 0b101010 42
💡 ヒント: 数字の前にうっかり 0 を付けると 8 進数になってしまいます。int x = 010; は 10 ではなく 8 になります——初心者によくあるバグです。

整数リテラルのデフォルトの型は int です。値が int の範囲を超えると自動的に longlong long になります。型を手動で指定することもできます:

接尾辞 意味
u または U unsigned 42U
l または L long 42L
ll または LL long long 42LL
UL unsigned long 42UL
⚠️ 注意: 小文字の l ではなく大文字の L を使ってください。l は数字の 1 と紛らわしいです。42l421 に見えます。

浮動小数点リテラル

C
double a = 3.14;
double b = 2.0e8;
double c = 1.5E-3;
float  d = 3.14f;

浮動小数点リテラルのデフォルトの型は double です。float にするには f または F 接尾辞を、long double にするには l/L 接尾辞を付けます。

🔥 よくある間違い:

C
float f = 3.14;

3.14double リテラルです。これを float 変数に代入すると暗黙の切り詰めが発生し、コンパイラが警告を出す可能性があります。正しい書き方:

C
float f = 3.14f;

文字リテラル

単一引用符で囲んだ 1 文字です:

C
char ch = 'A';
char newline = '\n';
char zero = '\0';

C言語では文字リテラルの型は intchar ではない)です——見落としやすい微妙な詳細です:

C
printf("%zu\n", sizeof('A'));
TEXT
4

C言語では sizeof('A') は 4(int のサイズ)ですが、C++ では 1(char のサイズ)です。これは C言語と C++ の微妙な違いです。

文字列リテラル

二重引用符で囲んだ文字の並びです:

C
printf("Hello, World!\n");

文字列リテラルの型は char[] です。コンパイラが自動的に末尾に \0 を追加するため、"abc" は実際には 4 バイト(a、b、c、\0)を占めます。

⚠️ 注意: 文字列リテラルは読み取り専用メモリに格納されます。変更しようとすると未定義動作になります(通常はクラッシュ):

C
char *s = "hello";
s[0] = 'H';

const修飾子

const は C言語のキーワードで、変数を「読み取り専用」にします——初期化後は変更できません:

C
const int MAX_SIZE = 100;
const float PI = 3.14159f;
const char NEWLINE = '\n';

const 変数を変更しようとするとコンパイルエラーになります:

C
const int MAX_SIZE = 100;
MAX_SIZE = 200;
TEXT
error: assignment of read-only variable 'MAX_SIZE'

constと配列

const 変数で配列のサイズを指定できるでしょうか?

C
const int SIZE = 10;
int arr[SIZE];
⚠️ 注意: C89 ではこれは許可されていません——配列のサイズはコンパイル時定数でなければならず、C言語の const int はコンパイル時定数ではなく(単なる実行時の読み取り専用変数です)。C99 では可変長配列(VLA)が導入されたため上記のコードはコンパイルできますが、VLA はすべてのコンパイラがサポートするオプション機能です。

配列サイズに真のコンパイル時定数が必要な場合は、#define または enum を使ってください。

constとポインタ

const とポインタの組み合わせは C言語の古典的な難問です。3 つのパターンがあります:

C
int value = 42;

const int *p1 = &value;
int * const p2 = &value;
const int * const p3 = &value;
宣言 意味
const int *p 指す先のデータは変更不可。ポインタ自体は別のアドレスを指せる
int * const p ポインタ自体は変更不可(固定アドレス)。指す先のデータは変更可能
const int * const p ポインタも指す先のデータも変更不可
💡 ヒント: const* のどちら側にあるかを見てください。左側にあればデータを修飾し、右側にあればポインタを修飾します。

#defineマクロ定数

#define はマクロ定数を定義するためのプリプロセッサディレクティブです:

C
#define PI 3.14159
#define MAX_SIZE 100
#define NEWLINE '\n'

プリプロセッサはコンパイル前にすべての PI3.14159 に置換します。変数ではなく、メモリを消費せず、型チェックもありません。

#defineとconstの比較

特徴 #define const
処理される時期 プリプロセス段階(テキスト置換) コンパイル段階
型チェック なし あり
スコープ 定義からファイル末尾まで 変数のスコープ規則に従う
デバッグ デバッガでマクロ名は見えない デバッガで const 変数は見える
アドレス取得 不可 可能
💡 ヒント: const を優先しましょう——型チェックとスコープ制限があり、より安全です。条件コンパイルフラグやコンパイル時定数が必要な場合に #define を使ってください。

マクロのよくある落とし穴

C
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;

3 + 3 * 2 = 12 を期待するかもしれませんが、マクロは 3 + 3 * 2 = 9 に展開されます。マクロは純粋なテキスト置換であり、演算子の優先順位を考慮しません。

修正: マクロに括弧を追加します:

C
#define DOUBLE(x) ((x) + (x))

列挙型定数

enum は整数定数を定義するもう一つの方法です:

C
enum Color {
    RED,
    GREEN,
    BLUE
};

デフォルトでは値は 0 から始まり 1 ずつ増加します:RED = 0GREEN = 1BLUE = 2。値を手動で割り当てることもできます:

C
enum Weekday {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI,
    SAT = 100,
    SUN
};

TUE = 2WED = 3、……、SUN = 101(後続の値は前の値より 1 大きくなります)。

列挙型の値は真のコンパイル時定数であり、配列サイズなどに使えます。#define と比べて型情報を持ち、デバッガでも名前が見えるため、可能であれば列挙型を優先しましょう。

暗黙の型変換

異なる型が混在する式では、C言語は自動的に型変換を行います。暗黙の変換規則を理解することがバグを防ぐ鍵です。

整数の昇格

式の中では、charshort は演算の前に自動的に int に昇格されます:

C
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
TEXT
4

a + b の結果は int(4 バイト)であり、char(1 バイト)ではありません。

算術変換

異なる型の値が演算で組み合わさると、低位の型は自動的に高位の型に変換されます。低位から高位への変換階層:

TEXT
int → unsigned int → long → unsigned long → long long → unsigned long long → float → double → long double

規則:演算の前に、低位のオペランドが自動的に高位の型に変換されます。

C
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
TEXT
2.5

a が自動的に double に変換され、浮動小数点除算が行われて 2.5 になります。

代入変換

代入時、右辺の値は左辺の型に自動的に変換されます:

C
int x = 3.14;
double y = 42;

3.14int に代入すると 3 に切り詰められ、42double に代入すると 42.0 になります。

⚠️ 注意: 浮動小数点値を整数型に代入すると、小数部分は単に切り捨てられます(四捨五入ではありません):

C
int n = 2.9;

n は 2 であり、3 ではありません。

整数除算の落とし穴

これは初心者にとって最もよくある落とし穴です:

C
int a = 5;
int b = 2;
double result = a / b;
printf("%.1f\n", result);
TEXT
2.0

2.5 を期待するのに 2.0 になります。理由は:a / b が整数除算であり、結果は 2(整数)で、それが double 値の 2.0 に変換されるからです。

修正: 少なくとも一方のオペランドを浮動小数点にします:

C
double result = (double)a / b;

または:

C
double result = a * 1.0 / b;

または:

C
double result = 5.0 / 2;

明示的型キャスト

暗黙の変換で目的が達せない場合、明示的キャストでコンパイラに望む型を指示できます:

構文

C
(型名)式

C
#include <stdio.h>

int main(void) {
    int a = 7;
    int b = 2;

    printf("整数除算: %d\n", a / b);
    printf("キャスト付き: %.2f\n", (double)a / b);

    return 0;
}
▶ 試してみよう
TEXT
整数除算: 3
キャスト付き: 3.50

(double)a はまず a を 7.0 に変換します。その後 b と組み合わさると、b も暗黙的に double に昇格し、浮動小数点除算が行われます。

C
#include <stdio.h>
#include <math.h>

int main(void) {
    double x = 3.7;

    printf("直接切り捨て: %d\n", (int)x);
    printf("四捨五入: %d\n", (int)(x + 0.5));
    printf("round() 関数: %.0f\n", round(x));

    return 0;
}
▶ 試してみよう
TEXT
直接切り捨て: 3
四捨五入: 4
round() 関数: 4
💡 ヒント: C99 では浮動小数点から整数への切り詰めはゼロ方向への丸めと規定されています。したがって (int)(-3.7)-4 ではなく -3 になります。これは「床関数」(負の無限大方向への丸め)とは異なるので注意してください。

型変換のよくある落とし穴

符号付きと符号なしの混在

C
int a = -5;
unsigned int b = 10;

if (a + b > 0) {
    printf("0 より大きい\n");
} else {
    printf("0 以下\n");
}
TEXT
0 より大きい

-5 + 10 = 5 > 0 で正しいように思えますが、このコードの動作は暗黙の変換規則に依存します。intunsigned int が演算で混在すると、intunsigned int に変換されます。-5 は符号なしとして解釈すると非常に大きな正の数になるため、結果は確かに 0 より大きくなります——しかしおそらく意図したロジックではないでしょう。

⚠️ 注意: 符号付きと符号なしの整数を演算で混在させないでください。 やむを得ない場合は明示的キャストを使ってください。

精度の損失

C
int big = 123456789;
float f = big;
printf("%d\n", big);
printf("%.0f\n", f);
TEXT
123456789
123456792

float は有効桁 6-7 桁しかなく、9 桁の整数を正確に表せません——末尾 3 桁が失われます。大きな整数を float に代入する際は十分注意してください。

オーバーフロー変換

C
char ch = 200;
printf("%d\n", ch);

char が符号付きのプラットフォームでは、200 は char の範囲(-128 ~ 127)を超えるため未定義動作になります——おそらく -56 が出力されます。0 ~ 255 の値を格納する必要がある場合は unsigned char を使ってください。

ポインタ変換

異なる型のポインタ間のキャストは危険です:

C
int value = 0x41424344;
char *p = (char *)&value;
printf("%c\n", *p);

出力はシステムのバイトオーダー(ビッグエンディアンかリトルエンディアンか)に依存し、移植性がありません。この種の変換は低レイヤープログラミングでのみ使われ、初心者は避けるべきです。

❓ よくある質問

Q #define と const はどちらを使うべきですか?
A const を優先してください。型チェックがあり、スコープ規則に従い、デバッガにも対応しています。#define は純粋なテキスト置換で型安全性がありませんが、コンパイル時定数として配列サイズや条件コンパイルに使えます。値がコンパイル時に必要な場合(配列サイズなど)は #define または enum を、それ以外は const を使ってください。
Q なぜ 5/2 は 2.5 ではなく 2 になるのですか?
A 5 と 2 はどちらも int であり、C言語では整数除算の結果は整数になると規定されているからです——小数部分は切り捨てられます。2.5 を得るには、少なくとも一方のオペランドを浮動小数点にしてください:5.0/2 または (double)5/2。これは C言語の最もよくある初心者の落とし穴の一つです。
Q キャストは元の変数の値を変更しますか?
A いいえ。キャストは一時的な変換値を生成するだけで、元の変数の値も型も変更されません。例えば、(int)3.7 は一時的な値 3 を生成しますが、3.7 は依然として 3.7 です。
Q なぜ符号付きと符号なしの演算の混在はバグを生みやすいのですか?
A C言語の規格では、混在時に符号付きの値が暗黙的に符号なしに変換されると規定されているからです。負の数は符号なしになると非常に大きな正の値になり、比較や計算が直感に反する結果になります。混在を避けるか、演算前に明示的キャストを使ってください。

📖 まとめ

📝 練習問題

  1. 数値 255 を 10 進数、16 進数、8 進数で出力するプログラムを書き、出力形式を観察してください
  2. 円の面積を計算するプログラムを書いてください(半径は int の 10、円周率は const double で定義)。整数除算の落とし穴に注意すること
  3. 次の値をテストして結果を記録するプログラムを書いてください:(int)3.9 の値、(int)(-3.9) の値、char ch = 128 の %d 出力。それぞれの結果を説明してください
100%