はじめに
Hello World
すべての C プログラムは main() から始まります。#include <stdio.h> は標準 I/O ライブラリ(printf、scanf)を取り込みます。main は成功時に 0、失敗時に非ゼロを返します。void キーワードは main がパラメータを取らないことを明示的に宣言します。
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}変数と型
C は静的型付けです。一般的な型:int、float、double、char。float には f サフィックスが必要です。char[] は文字列です(ヌル終端配列)。long と short はサイズ修飾子です。unsigned は非負を意味します。サイズはプラットフォームにより異なります;固定幅には <stdint.h> を使用してください。
int age = 30;
float height = 5.7f;
double pi = 3.14159;
char grade = 'A';
char name[] = "Alice";
long big = 100000L;
unsigned int count = 42;
printf("%s is %d\n", name, age);入力と出力
scanf は入力を保存する変数のアドレス(&)が必要です。バッファオーバーフローを防ぐため、文字列入力の長さを常に制限してください(50文字バッファには %49s)。scanf は空白で文字列の読み取りを停止します;完全な行には fgets を使用してください。
int n;
printf("Enter a number: ");
scanf("%d", &n);
printf("You entered %d\n", n);
char name[50];
printf("Enter name: ");
scanf("%49s", name); // limit to prevent overflow
printf("Hi, %s!\n", name);printf フォーマット指定子
フォーマット指定子が出力を制御します:%d 整数、%f 浮動小数点、%c 文字、%s 文字列、%x 16進数、%p ポインタ。幅と精度(例:%5.2f)が配置と小数を制御します。指定子と型の不一致は未定義動作を引き起こします。
printf("%d\n", 42); // integer
printf("%f\n", 3.14); // float/double
printf("%.2f\n", 3.14159); // 3.14 (2 decimals)
printf("%c\n", 'A'); // char
printf("%s\n", "hello"); // string
printf("%x\n", 255); // ff (hex)
printf("%5d\n", 42); // right-aligned, width 5
printf("%-5d|\n", 42); // left-alignedプリプロセッサとヘッダ
プリプロセッサはコンパイル前に実行されます。#include はヘッダファイルを貼り付け、#define はマクロと定数を作成します。二重インクルードを防ぐため、ヘッダには常にインクルードガード(#ifndef/#define/#endif)を使用してください。マクロはテキスト置換です — パラメータの周りに括弧を使用してください。
#include <stdio.h> // system header
#include "myheader.h" // local header
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
#ifndef GUARD_H
#define GUARD_H
// header content
#endif文字列と String.h
文字列の基礎
C の文字列はヌル終端 char 配列です。strlen は '\0' の前の文字数をカウントします;sizeof はバッファサイズを返します。strcpy はヌルターミネータまでコピーします — オーバーフローを避けるため、宛先が十分に大きいことを常に確認してください。
#include <string.h>
char s[20] = "Hello";
printf("Length: %zu\n", strlen(s)); // 5
printf("Size: %zu\n", sizeof(s)); // 20
char dest[20];
strcpy(dest, s); // copy
printf("%s\n", dest); // Hello連結と比較
strcat は追加します(宛先に余裕が必要)。strcmp は辞書順に比較します:等しければ 0、最初 < 2番目なら負、最初 > 2番目なら正を返します。文字列の比較に == を使用しないでください(内容ではなくポインタを比較します)。
#include <string.h>
char s[30] = "Hello";
strcat(s, ", World!"); // s = "Hello, World!"
printf("%s\n", s);
int cmp = strcmp("apple", "banana");
// returns <0 if a<b, 0 if equal, >0 if a>b
if (strcmp(s, "Hello") == 0) {
printf("Equal!\n");
}sprintf と sscanf
sprintf は文字列バッファにフォーマットします (printf のようなものですが文字列へ)。sscanf は文字列から変数に解析します(scanf のようなものですが文字列から)。最大サイズを指定してバッファオーバーフローを防ぐため、sprintf の代わりに snprintf を使用してください。
char buf[100];
int age = 30;
char name[] = "Alice";
sprintf(buf, "%s is %d years old", name, age);
printf("%s\n", buf);
int a, b;
sscanf("10 20", "%d %d", &a, &b);
printf("a=%d, b=%d\n", a, b); // a=10, b=20strchr、strstr と strtok
strchr は文字を検索し、strstr は部分文字列を検索します。strtok は区切り文字で文字列を分割しますが、元の文字列を変更し(ヌルターミネータを挿入)、スレッドセーフではありません — トークン化を続けるには後続の呼び出しで NULL を渡します。
#include <string.h>
char s[] = "Hello, World!";
char *p = strchr(s, 'W'); // find first 'W'
printf("%s\n", p); // World!
char *sub = strstr(s, "World");
printf("%s\n", sub); // World!
char tokens[] = "a,b,c";
char *tok = strtok(tokens, ",");
while (tok) {
printf("%s\n", tok);
tok = strtok(NULL, ",");
}fgets と安全な入力
fgets は文字列を読み取る安全な方法です — オーバーフローを防ぐためのサイズ制限を取ります。scanf とは異なり、スペースを読み取ります。改行は結果に含まれます;strcspn で見つけて削除します。gets(C11 で削除)より常に fgets を優先してください。
char line[100];
printf("Enter text: ");
fgets(line, sizeof(line), stdin);
// removes trailing newline
line[strcspn(line, "\n")] = 0;
printf("You said: %s\n", line);数値と数学
整数型と制限
正確なサイズが必要な場合は固定幅型(int32_t、int64_t)に <stdint.h> を使用してください。<limits.h> はプラットフォーム固有の境界の INT_MAX、INT_MIN などを提供します。LL サフィックスは long long リテラルをマークします。int/long のサイズはプラットフォームにより異なります。
#include <stdint.h>
#include <limits.h>
int32_t a = 100;
int64_t big = 9223372036854775807LL;
uint8_t byte = 255;
printf("INT_MAX = %d\n", INT_MAX); // 2147483647
printf("INT_MIN = %d\n", INT_MIN); // -2147483648
printf("UINT_MAX = %u\n", UINT_MAX); // 4294967295浮動 小数点
float は 4 バイト(6-7 桁精度)、double は 8 バイト(15-16 桁)です。丸めエラーのため float を == で比較しないでください — fabs(a - b) < epsilon を使用します。<float.h> は DBL_MAX、DBL_EPSILON を提供します。
#include <float.h>
double d = 3.141592653589793;
float f = 3.14f;
printf("DBL_MAX = %e\n", DBL_MAX);
printf("DBL_EPSILON = %e\n", DBL_EPSILON);
if (d == 0.1 + 0.2) {
// likely false! floating point imprecision
}数学関数
<math.h> は標準数学関数を提供します。pow と sqrt は double を返します。fabs は abs の浮動小数点版です(abs は int 用)。一部のシステムでは -lm でリンクします。金融計算では浮動小数点を避け — 整数セントを使用してください。
#include <math.h>
double x = 2.5;
pow(x, 3); // 15.625
sqrt(x); // 1.581
fabs(-5.0); // 5.0
floor(3.7); // 3.0
ceil(3.2); // 4.0
fmod(10.5, 3); // 1.5
exp(1); // 2.718 (e^1)
log(2.718); // 1.0 (natural log)乱数
rand() は 0 から RAND_MAX の疑似乱数 int を返します。プログラム開始時に srand() で一度シードします(time(NULL) を使用)。rand() % N はモジュロバイアスがあり品質が低いです;本格的な使用には /dev/urandom を読むかサードパーティ PRNG ライブラリを使用してください。
#include <stdlib.h>
#include <time.h>
srand(time(NULL)); // seed once at start
int r = rand() % 100; // 0-99
int dice = rand() % 6 + 1; // 1-6
float fr = (float)rand() / RAND_MAX; // 0.0 - 1.0型変換とキャスト
キャストは (type)value です。整数除算は切り捨てられます — float 結果を得るには float オペランドを使用してください。atoi/atof は文字列を数値に変換しますがエラーチェックをしません;errno で解析エラーを報告する strtol/strtod を優先してください。
int i = 65;
char c = (char)i; // 'A'
double d = 3.99;
int truncated = (int)d; // 3
// Implicit promotion
int a = 5;
double result = a / 2.0; // 2.5 (promoted to double)
int bad = a / 2; // 2 (integer division)
// String to number
int n = atoi("42");
double f = atof("3.14");制御フロー
If / Else
if/else if/else が標準の条件分岐です。C は 0 を false、非ゼロを true として扱います。後から行を追加する時のバグを 防ぐため、単一文でもブレースを使用してください。C89 には boolean 型がありません;C99 は _Bool と <stdbool.h> を追加します。
int score = 85;
if (score >= 90) {
printf("A\n");
} else if (score >= 80) {
printf("B\n");
} else if (score >= 70) {
printf("C\n");
} else {
printf("F\n");
}Switch
switch はマッチする case ラベルにジャンプします。フォールスルーを防ぐため常に break を使用してください(case 6 と 7 は意図的にコードを共有)。default はマッチしない値を処理します。switch は整数型と char 型のみで動作し、文字列や float では動作しません。
int day = 3;
switch (day) {
case 1: printf("Mon\n"); break;
case 2: printf("Tue\n"); break;
case 3: printf("Wed\n"); break;
case 6:
case 7: printf("Weekend\n"); break;
default: printf("Invalid\n");
}For ループ
for ループは init; condition; update を持ちます。sizeof(nums)/sizeof(nums[0]) はコンパイル時に配列長を計算します。for ループ内で i を宣言するには C99 以降が必要です。条件が最初から false の場合、ループ本体はゼロ回実行されます。
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
// Iterate an array
int nums[] = {10, 20, 30};
int n = sizeof(nums) / sizeof(nums[0]);
for (int i = 0; i < n; i++) {
printf("%d\n", nums[i]);
}While と Do-While
while は実行前にチェックします(ゼロ回実行の可能性)。do-while は本体を先に実行し、その後チェックします(少なくとも1回実行)。do-while は入力検証とメニューループに最適です — プロンプトが条件チェックの前に表示される必要があるため。
int count = 0;
while (count < 3) {
printf("%d\n", count++);
}
int x;
do {
printf("Enter positive: ");
scanf("%d", &x);
} while (x <= 0); // runs at least onceBreak、Continue と goto
break は最も近いループ/switch を終了;continue は次の反復にスキップします。C にはラベル付き break がないため、深くネストされたループから抜けるには goto が慣用的な方法です。goto はそれ以外では推奨されませんが、クリーンアップパターンとネストループ脱出には許容されます。
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // skip 3
if (i == 7) break; // stop at 7
printf("%d ", i); // 0 1 2 4 5 6
}
// goto for breaking nested loops
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (found) goto done;
}
}
done: printf("exited\n");関数
定義と呼び出し
関数は使用前に宣言(プロトタイプ)または定義しなければなりません。void 戻り値型は戻り値がないことを意味します。const char *name は関数が文字列を変更しないことを意味します。C は値渡しで引数を渡します;参照渡しをシミュレートするにはポインタを使用します。
int add(int a, int b) {
return a + b;
}
void greet(const char *name) {
printf("Hello, %s!\n", name);
}
int main(void) {
int sum = add(3, 4);
greet("Alice");
return 0;
}再帰
再帰はより小さい入力で自身を呼び出します。すべての再帰関数には停止するベースケースが必要です。上記の単純な fib は O(2^n) です — 指数関数的です。効率のためにメモ化または反復を使用してください。深い再帰はコールスタックをオーバーフローする可能性があります。
int factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
// factorial(5) == 120関数ポインタ
関数ポインタは関数のアドレスを格納し、コールバックと動的ディスパッチを可能にします。構文 int (*op)(int, int) は2つの int を取り int を返す関数へのポインタを宣言します。qsort、イベントハンドラ、プラグインシステムで使用されます。
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int (*op)(int, int) = add;
printf("%d\n", op(3, 4)); // 7
op = sub;
printf("%d\n", op(3, 4)); // -1
// As a parameter
int apply(int (*f)(int, int), int a, int b) {
return f(a, b);
}可変長引数関数
可変長引数関数は <stdarg.h> を使用して可変個の引数を受け取ります。va_start が初期化し、va_arg が次の引数を取得し、va_end がクリーンアップします。カウントを知る方法が必要です(例:カウントパラメータやセンチネル値)。printf はこのように動作します。
#include <stdarg.h>
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
// sum(3, 10, 20, 30) == 60static と inline
関数/変数の static は現在の翻訳単位(ファイル)に制限します。ローカル変数の static は呼び出し間で持続させます(グローバルのようにスコープされます)。inline はコンパイラに関数本体を埋め込むことを提案します;モダンコンパイラ はそれを無視し、自分で決定します。
// static: internal linkage (file-local)
static int counter = 0;
static int next_id(void) { return ++counter; }
// inline: hint to expand inline
static inline int square(int x) { return x * x; }
// static local: persists across calls
int call_count(void) {
static int n = 0;
return ++n;
}配列とポインタ
配列
配列は固定サイズ、ゼロインデックス、メモリに連続して格納されます。sizeof(arr)/sizeof(arr[0]) は長さを計算しますが、実際の配列でのみ動作し、ポインタでは動作しません(配列は関数に渡されるとポインタに崩壊し、サイズ情報を失います)。
int nums[5] = {1, 2, 3, 4, 5};
printf("%d\n", nums[0]); // 1
printf("%d\n", nums[4]); // 5
int len = sizeof(nums) / sizeof(nums[0]); // 5
// Array of strings
char *fruits[] = {"apple", "banana", "cherry"};
printf("%s\n", fruits[1]); // bananaポインタ
ポインタはメモリアドレスを格納します。& はアドレスを取得し、* はデリファレンスします。ポインタは常に初期化してください(未割り当てなら NULL を使用)。NULL や未初期化ポインタのデリファレンスは未定義動作です(通常クラッシュ)。デリファレンス前に NULL チェックしてください。
int x = 10;
int *ptr = &x; // ptr holds address of x
printf("%p\n", (void*)ptr); // address
printf("%d\n", *ptr); // 10 (dereference)
*ptr = 20; // modify x through pointer
printf("%d\n", x); // 20
int *p = NULL; // null pointer (points to nothing)
if (p) { /* safe to dereference */ }ポインタ演算
ポインタ演算は要素サイズでスケールします:p+1 は次のバイトではなく次の要素に移動します。これにより p[i] は *(p+i) と等価になります。同じ配列への2つのポインタの減算は要素数を与えます。ポインタ演算は配列内でのみ有効です。
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // points to arr[0]
printf("%d\n", *p); // 10
printf("%d\n", *(p + 1)); // 20
printf("%d\n", *(p + 2)); // 30
p += 3; // now points to arr[3]
printf("%d\n", *p); // 40
int diff = (p - arr); // 3 (number of elements)配列とポインタ
配列名は関数に渡される時や式で使用される時にポインタに崩壊し、サイズ情報を失います。こ れが配列長を別途渡さなければならない理由です。sizeof(arr) は arr がポインタではなく真の配列の場合にのみ完全な配列サイズを与えます。
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // arr decays to &arr[0]
// These are equivalent
printf("%d\n", arr[2]);
printf("%d\n", ptr[2]);
printf("%d\n", *(arr + 2));
// But sizeof differs
printf("%zu\n", sizeof(arr)); // 20 (5 * 4 bytes)
printf("%zu\n", sizeof(ptr)); // 8 (pointer size)多次元配列
2次元配列は配列の配列で、行優先で格納されます。grid[i][j] は行 i、列 j にアクセスします。関数に渡す場合、列数を指定しなければなりません:void foo(int arr[][3], int rows)。動的2次元配列にはポインタの配列を使用します。
int grid[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printf("%d\n", grid[0][1]); // 2
printf("%d\n", grid[1][2]); // 6
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", grid[i][j]);
}
printf("\n");
}構造体とユニオン
構造体
構造体は異なる型の関連変数をグループ化します。メンバはドット演算子(.)でアクセスします。ブレース記法で初期化します。構造体は値渡しされます(コピー);コピーを避け元を変更するにはポインタ渡し(struct Point *)を使用します。
struct Point {
int x;
int y;
};
struct Point p = {3, 4};
printf("(%d, %d)\n", p.x, p.y); // (3, 4)
p.x = 10;
p.y = 20;
printf("(%d, %d)\n", p.x, p.y); // (10, 20)typedef
typedef は型のエイリアスを作成し、struct Student の代わりに Student と書けます。構造体でよく使用され構文を簡素化します。typedef は関数ポインタ型にもエイリアスでき、コールバックをはるかに読みやすくします。
typedef struct {
char name[50];
int age;
float gpa;
} Student;
Student s = {"Alice", 20, 3.8};
printf("%s: %d, GPA %.1f\n", s.name, s.age, s.gpa);
// typedef for other types
typedef unsigned long ulong;
typedef int (*CompareFn)(const void*, const void*);構造体へのポインタ
構造体へのポインタがある場合、メンバにアクセスするにはアロー演算子(->)を使用します。ptr->x は (*ptr).x の短縮形です。効率のため(大きな構造体のコピーを避ける)と変更を許可するために関数に構造体ポ インタを渡します。
typedef struct {
int x, y;
} Point;
Point p = {3, 4};
Point *ptr = &p;
// Arrow operator (->) for pointer members
printf("%d\n", ptr->x); // 3
ptr->y = 10;
printf("%d\n", p.y); // 10
// Equivalent: (*ptr).xユニオン
ユニオンは複数の型を同じメモリに重ね合わせます — 一度に有効なメンバは1つだけです。あるメンバを設定すると他を上書きします。タイプパニング(ビットの再解釈)と、複数の型のうち1つだけが一度に必要な場合のメモリ節約に便利です。
union Value {
int i;
float f;
char bytes[4];
};
union Value v;
v.i = 65;
printf("%d\n", v.i); // 65
printf("%c\n", v.bytes[0]); // 'A' (same memory)
v.f = 3.14f;
printf("%d\n", v.i); // reinterpreted bits!ビットフィールドと列挙型
ビットフィールドは複数の小さなフィールドを1つの int にパックし、メモリを節約します(プロトコルとハードウェアレジスタで一般的)。列挙型は名前付き整数定数を定義します(デフォルトで 0、1、2...)。より良いデバッグと型安全性のために #define の代わりに列挙型を使用してください。
struct Flags {
unsigned int bold : 1;
unsigned int italic : 1;
unsigned int size : 6; // 0-63
};
struct Flags f = {1, 0, 12};
printf("bold=%d, size=%d\n", f.bold, f.size);
enum Color { RED, GREEN, BLUE };
enum Color c = GREEN;
printf("%d\n", c); // 1メモリ管理
malloc と free
malloc はヒープメモリを割り当て void ポインタを返します(失敗時は NULL)。常に NULL をチェックしてください。メモリリークを避けるため、すべての malloc は free とペアにしなければなりません。free 後にポインタを NULL に設定すると use-after-free バグを防ぎます。
#include <stdlib.h>
int *arr = malloc(5 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "malloc failed\n");
return 1;
}
for (int i = 0; i < 5; i++) arr[i] = i * 2;
free(arr); // release memory
arr = NULL; // avoid dangling pointercalloc と realloc
calloc はメモリを割り当てゼロクリアします(ゴミがある malloc より安全)。realloc はサイズ変更します:ブロックを移動し新しいポインタを返す場合があります。realloc が失敗した場合 NULL を返しますが元のブロックは有効 — リークを避け るため一時ポインタを使用します。
#include <stdlib.h>
// calloc: zero-initialized
int *arr = calloc(5, sizeof(int)); // all zeros
// realloc: resize
arr = realloc(arr, 10 * sizeof(int));
if (!arr) { /* handle failure, original still valid */ }
free(arr);