定数と型変換
定数は数学の円周率のようなものです——一度決めたら変わりません。型変換は両替のようなものです:異なる通貨には固定のレートがありますが、変換時の精度の低下には注意が必要です。
リテラル
リテラルはコードに直接書く固定値です。計算の必要がなく、一目で値がわかります。
整数リテラル
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 |
int x = 010; は 10 ではなく 8 になります——初心者によくあるバグです。
整数リテラルのデフォルトの型は int です。値が int の範囲を超えると自動的に long や long long になります。型を手動で指定することもできます:
| 接尾辞 | 意味 | 例 |
|---|---|---|
u または U |
unsigned | 42U |
l または L |
long | 42L |
ll または LL |
long long | 42LL |
UL |
unsigned long | 42UL |
l ではなく大文字の L を使ってください。l は数字の 1 と紛らわしいです。42l は 421 に見えます。
浮動小数点リテラル
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 接尾辞を付けます。
float f = 3.14;
3.14 は double リテラルです。これを float 変数に代入すると暗黙の切り詰めが発生し、コンパイラが警告を出す可能性があります。正しい書き方:
float f = 3.14f;
文字リテラル
単一引用符で囲んだ 1 文字です:
char ch = 'A';
char newline = '\n';
char zero = '\0';
C言語では文字リテラルの型は int(char ではない)です——見落としやすい微妙な詳細です:
printf("%zu\n", sizeof('A'));
4
C言語では sizeof('A') は 4(int のサイズ)ですが、C++ では 1(char のサイズ)です。これは C言語と C++ の微妙な違いです。
文字列リテラル
二重引用符で囲んだ文字の並びです:
printf("Hello, World!\n");
文字列リテラルの型は char[] です。コンパイラが自動的に末尾に \0 を追加するため、"abc" は実際には 4 バイト(a、b、c、\0)を占めます。
char *s = "hello";
s[0] = 'H';
const修飾子
const は C言語のキーワードで、変数を「読み取り専用」にします——初期化後は変更できません:
const int MAX_SIZE = 100;
const float PI = 3.14159f;
const char NEWLINE = '\n';
const 変数を変更しようとするとコンパイルエラーになります:
const int MAX_SIZE = 100;
MAX_SIZE = 200;
error: assignment of read-only variable 'MAX_SIZE'
constと配列
const 変数で配列のサイズを指定できるでしょうか?
const int SIZE = 10;
int arr[SIZE];
const int はコンパイル時定数ではなく(単なる実行時の読み取り専用変数です)。C99 では可変長配列(VLA)が導入されたため上記のコードはコンパイルできますが、VLA はすべてのコンパイラがサポートするオプション機能です。
配列サイズに真のコンパイル時定数が必要な場合は、#define または enum を使ってください。
constとポインタ
const とポインタの組み合わせは C言語の古典的な難問です。3 つのパターンがあります:
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 はマクロ定数を定義するためのプリプロセッサディレクティブです:
#define PI 3.14159
#define MAX_SIZE 100
#define NEWLINE '\n'
プリプロセッサはコンパイル前にすべての PI を 3.14159 に置換します。変数ではなく、メモリを消費せず、型チェックもありません。
#defineとconstの比較
| 特徴 | #define |
const |
|---|---|---|
| 処理される時期 | プリプロセス段階(テキスト置換) | コンパイル段階 |
| 型チェック | なし | あり |
| スコープ | 定義からファイル末尾まで | 変数のスコープ規則に従う |
| デバッグ | デバッガでマクロ名は見えない | デバッガで const 変数は見える |
| アドレス取得 | 不可 | 可能 |
const を優先しましょう——型チェックとスコープ制限があり、より安全です。条件コンパイルフラグやコンパイル時定数が必要な場合に #define を使ってください。
マクロのよくある落とし穴
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;
3 + 3 * 2 = 12 を期待するかもしれませんが、マクロは 3 + 3 * 2 = 9 に展開されます。マクロは純粋なテキスト置換であり、演算子の優先順位を考慮しません。
修正: マクロに括弧を追加します:
#define DOUBLE(x) ((x) + (x))
列挙型定数
enum は整数定数を定義するもう一つの方法です:
enum Color {
RED,
GREEN,
BLUE
};
デフォルトでは値は 0 から始まり 1 ずつ増加します:RED = 0、GREEN = 1、BLUE = 2。値を手動で割り当てることもできます:
enum Weekday {
MON = 1,
TUE,
WED,
THU,
FRI,
SAT = 100,
SUN
};
TUE = 2、WED = 3、……、SUN = 101(後続の値は前の値より 1 大きくなります)。
列挙型の値は真のコンパイル時定数であり、配列サイズなどに使えます。#define と比べて型情報を持ち、デバッガでも名前が見えるため、可能であれば列挙型を優先しましょう。
暗黙の型変換
異なる型が混在する式では、C言語は自動的に型変換を行います。暗黙の変換規則を理解することがバグを防ぐ鍵です。
整数の昇格
式の中では、char と short は演算の前に自動的に int に昇格されます:
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
4
a + b の結果は int(4 バイト)であり、char(1 バイト)ではありません。
算術変換
異なる型の値が演算で組み合わさると、低位の型は自動的に高位の型に変換されます。低位から高位への変換階層:
int → unsigned int → long → unsigned long → long long → unsigned long long → float → double → long double
規則:演算の前に、低位のオペランドが自動的に高位の型に変換されます。
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
2.5
a が自動的に double に変換され、浮動小数点除算が行われて 2.5 になります。
代入変換
代入時、右辺の値は左辺の型に自動的に変換されます:
int x = 3.14;
double y = 42;
3.14 を int に代入すると 3 に切り詰められ、42 を double に代入すると 42.0 になります。
int n = 2.9;
n は 2 であり、3 ではありません。
整数除算の落とし穴
これは初心者にとって最もよくある落とし穴です:
int a = 5;
int b = 2;
double result = a / b;
printf("%.1f\n", result);
2.0
2.5 を期待するのに 2.0 になります。理由は:a / b が整数除算であり、結果は 2(整数)で、それが double 値の 2.0 に変換されるからです。
修正: 少なくとも一方のオペランドを浮動小数点にします:
double result = (double)a / b;
または:
double result = a * 1.0 / b;
または:
double result = 5.0 / 2;
明示的型キャスト
暗黙の変換で目的が達せない場合、明示的キャストでコンパイラに望む型を指示できます:
構文
(型名)式
例
#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;
}
整数除算: 3
キャスト付き: 3.50
(double)a はまず a を 7.0 に変換します。その後 b と組み合わさると、b も暗黙的に double に昇格し、浮動小数点除算が行われます。
例
#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;
}
直接切り捨て: 3
四捨五入: 4
round() 関数: 4
(int)(-3.7) は -4 ではなく -3 になります。これは「床関数」(負の無限大方向への丸め)とは異なるので注意してください。
型変換のよくある落とし穴
符号付きと符号なしの混在
int a = -5;
unsigned int b = 10;
if (a + b > 0) {
printf("0 より大きい\n");
} else {
printf("0 以下\n");
}
0 より大きい
-5 + 10 = 5 > 0 で正しいように思えますが、このコードの動作は暗黙の変換規則に依存します。int と unsigned int が演算で混在すると、int が unsigned int に変換されます。-5 は符号なしとして解釈すると非常に大きな正の数になるため、結果は確かに 0 より大きくなります——しかしおそらく意図したロジックではないでしょう。
精度の損失
int big = 123456789;
float f = big;
printf("%d\n", big);
printf("%.0f\n", f);
123456789
123456792
float は有効桁 6-7 桁しかなく、9 桁の整数を正確に表せません——末尾 3 桁が失われます。大きな整数を float に代入する際は十分注意してください。
オーバーフロー変換
char ch = 200;
printf("%d\n", ch);
char が符号付きのプラットフォームでは、200 は char の範囲(-128 ~ 127)を超えるため未定義動作になります——おそらく -56 が出力されます。0 ~ 255 の値を格納する必要がある場合は unsigned char を使ってください。
ポインタ変換
異なる型のポインタ間のキャストは危険です:
int value = 0x41424344;
char *p = (char *)&value;
printf("%c\n", *p);
出力はシステムのバイトオーダー(ビッグエンディアンかリトルエンディアンか)に依存し、移植性がありません。この種の変換は低レイヤープログラミングでのみ使われ、初心者は避けるべきです。
❓ よくある質問
📖 まとめ
- リテラルには 4 種類あります:整数、浮動小数点、文字、文字列。接尾辞(f/L/UL)と基数プレフィクス(0x/0/0b)に注意
- const は変数を読み取り専用にします。#define より const を優先。#define は型チェックなしのプリプロセステキスト置換
- 暗黙の変換は整数の昇格と算術変換の規則に従います——低位の型は自動的に高位の型に変換
- 整数除算は最もよくある C言語の落とし穴:5/2 = 2。2.5 を得るには少なくとも一方のオペランドを浮動小数点にする必要がある
- 符号付きと符号なしの整数の混在は予期しない結果を生みます——避けるか明示的キャストを使う
📝 練習問題
- 数値 255 を 10 進数、16 進数、8 進数で出力するプログラムを書き、出力形式を観察してください
- 円の面積を計算するプログラムを書いてください(半径は int の 10、円周率は const double で定義)。整数除算の落とし穴に注意すること
- 次の値をテストして結果を記録するプログラムを書いてください:(int)3.9 の値、(int)(-3.9) の値、
char ch = 128の %d 出力。それぞれの結果を説明してください



