C言語はシステムプログラミングや組込み開発の基盤として広く用いられる静的型付け言語です。本稿では、変数宣言、制御フロー、配列、複合型(構造体・共用体・列挙型)といった基本要素を、実践的なコード例とともに簡潔に解説します。
1. データ型とメモリ表現
Cでは型がメモリ使用量と解釈方法を厳密に定義します。主な分類は以下の通りです:
- 基本型:整数(
int,long long)、文字(char)、浮動小数点(float,double) - 派生型:配列、ポインタ、関数ポインタ
- ユーザー定義型:構造体(
struct)、共用体(union)、列挙(enum)
各型のサイズはプラットフォーム依存ですが、sizeof演算子で実行時に確認できます:
#include <stdio.h>
int main(void) {
printf("Size of char: %zu\n", sizeof(char));
printf("Size of short: %zu\n", sizeof(short));
printf("Size of int: %zu\n", sizeof(int));
printf("Size of long: %zu\n", sizeof(long));
printf("Size of long long:%zu\n", sizeof(long long));
printf("Size of float: %zu\n", sizeof(float));
printf("Size of double: %zu\n", sizeof(double));
return 0;
}
典型的な64ビット環境では、intは4バイト、longとpointerは8バイトとなります。
2. 入出力フォーマット指定子
printfおよびscanfにおけるフォーマット文字列は型と正確に一致させる必要があります:
| 書式 | 対象型 | 備考 |
|---|---|---|
%d | signed int | 10進符号付き整数 |
%ld | long | 32/64ビット環境で差異あり |
%lld | long long | C99以降必須 |
%u | unsigned int | 符号なし10進表示 |
%x | unsigned int | 小文字16進 |
%o | unsigned int | 8進表示 |
%f | float / double | 出力時のみ。入力は%lf |
%c | char | 単一文字 |
%s | char * | ヌル終端文字列 |
以下は実用的な例です:
#include <stdio.h>
int main(void) {
int dec = 255;
unsigned int hex = 0xFFAB;
double pi = 3.1415926535;
printf("Decimal: %d\n", dec);
printf("Hex (lower): %x, (upper): %X\n", hex, hex);
printf("Pi with 3 decimals: %.3f\n", pi);
printf("Padded to width 10: %10d\n", dec);
printf("Zero-padded: %08d\n", dec);
return 0;
}
3. 制御構造
Cのフローコントロールは明示的かつ効率的です:
条件分岐
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else {
grade = 'C';
}
switch (mode) {
case MODE_FAST:
set_speed(200);
break;
case MODE_SLOW:
set_speed(50);
break;
default:
set_speed(100);
break;
}
反復処理
// while: 条件評価→実行
while (counter < limit) {
process_item(counter++);
}
// do-while: 実行→条件評価(最低1回保証)
do {
read_input(&buf);
} while (!is_valid(buf));
// for: 初期化・条件・更新を1行に集約
for (size_t i = 0; i < array_len; ++i) {
sum += data[i];
}
breakは直近のループまたはswitchを脱出、continueは現在のイテレーションをスキップします。
4. 配列とポインタの関係
配列名はその先頭アドレスを表す「定数ポインタ」です。1次元配列の宣言と操作:
int numbers[5] = {10, 20, 30, 40, 50};
printf("Array address: %p\n", (void*)numbers);
printf("First element address: %p\n", (void*)&numbers[0]);
printf("Size of entire array: %zu bytes\n", sizeof(numbers)); // 5 × sizeof(int)
2次元配列は行優先(row-major)でメモリに格納されます:
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// matrix[0][0] → matrix[0][1] → matrix[0][2] → matrix[1][0] … の順
5. 構造体によるデータのカプセル化
構造体は異なる型のメンバーを論理的に束ねる仕組みです:
typedef struct {
char title[64];
int year;
double rating;
} Movie;
Movie film = {"Inception", 2010, 8.8};
printf("Title: %s (%d), Rating: %.1f\n",
film.title, film.year, film.rating);
// 構造体ポインタ経由のアクセス
Movie *ptr = &film;
ptr->rating = 9.0;
6. 共用体によるメモリ共有
共用体は全メンバーが同一メモリ領域を共有し、最大メンバーのサイズを占めます:
union Data {
int integer;
float real;
char symbol;
};
union Data sample;
sample.integer = 42; // 整数を格納
printf("As int: %d\n", sample.integer); // 42
sample.real = 3.14f; // 浮動小数点で上書き
printf("As float: %.2f\n", sample.real); // 3.14
// 注意:前の値は失われる
7. 列挙型による意味的定義
列挙型は整数定数の集合に意味のある識別子を付与します:
typedef enum {
STATUS_IDLE = 0,
STATUS_RUNNING,
STATUS_PAUSED,
STATUS_COMPLETED
} ProcessState;
ProcessState current = STATUS_RUNNING;
8. ヘッダガードによる多重インクルード防止
ヘッダファイルの再定義エラーを防ぐため、以下のパターンが標準的です:
// utils.h
#ifndef UTILS_H
#define UTILS_H
int compute_checksum(const void* data, size_t len);
void log_message(const char* msg);
#endif // UTILS_H
代替として#pragma onceも使用されますが、非標準であり移植性に注意が必要です。