行優先メモリレイアウトから関数渡し、動的確保、行列・画像処理まで。配列の基礎を終えた人向け。
// 3行×4列 int m[3][4] = { { 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12} }; printf("%d\n", m[1][2]); // → 7 (1行目の2列目、0起点)
// 2層×3行×4列(立体的なイメージ) int cube[2][3][4]; for (int l = 0; l < 2; l++) for (int i = 0; i < 3; i++) for (int j = 0; j < 4; j++) cube[l][i][j] = l * 100 + i * 10 + j;
int m[3][4] = {0}; と書けば全要素0に初期化。int m[][4] = {{1,2,3,4},{5,6,7,8}}; と外側の次元だけ省略可能(列数は必須)。int m[3][4]; sizeof(m) // 48 (3 * 4 * 4バイト) sizeof(m[0]) // 16 (1行分 = 4 * 4バイト) sizeof(m[0][0]) // 4 (intひとつ) // 要素数を求めるイディオム: // 行数 = sizeof(m) / sizeof(m[0]) // 列数 = sizeof(m[0]) / sizeof(m[0][0])
int *p = (int *)m; p[i*COLS + j]
// 2次元配列を1次元のように辿る int m[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int *p = (int *)m; for (int k = 0; k < 12; k++) printf("%d ", p[k]); // → 1 2 3 4 5 6 7 8 9 10 11 12
// 列数を必ず書く(行数は省略可) void print_34(int m[][4], int rows) { for (int i = 0; i < rows; i++) for (int j = 0; j < 4; j++) printf("%d ", m[i][j]); } // 呼び出し int a[3][4]; print_34(a, 3);
// 行数・列数の両方を引数にしてから配列を受ける void print_any(int rows, int cols, int m[rows][cols]) { for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) printf("%d ", m[i][j]); } int a[3][4], b[2][5]; print_any(3, 4, a); print_any(2, 5, b);
// 1次元に "つぶして" 渡す。インデックスは自前で計算 void print_flat(int *m, int rows, int cols) { for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) printf("%d ", m[i * cols + j]); } int a[3][4]; print_flat((int *)a, 3, 4);
m[i][j] の記法が使えず読みづらい。// 各行の先頭ポインタを並べた配列を渡す void print_pp(int **m, int rows, int cols) { for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) printf("%d ", m[i][j]); } // 呼び出し側で「行ポインタの配列」を組む必要がある int row0[] = {1,2,3}; int row1[] = {4,5,6}; int *rows[] = {row0, row1}; print_pp(rows, 2, 3);
int a[3][4] を int ** にキャストしても動かない。メモリ表現が違う(前者は連続したint、後者は「ポインタの配列」)。混同しやすい最大の罠。#include <stdio.h> #include <stdlib.h> int main(void) { int rows = 3, cols = 4; // 全要素分をまとめて確保 int *m = malloc(sizeof(int) * rows * cols); if (!m) return 1; // 手動インデックス: m[i*cols + j] for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) m[i * cols + j] = i * 10 + j; free(m); return 0; }
m[i][j] 記法// 連続領域を確保しつつ、2次元インデックスを使う技 int (*m)[cols] = malloc(sizeof(int[rows][cols])); m[1][2] = 42; // 普通の添え字でアクセス可能 free(m);
int **)int **m = malloc(sizeof(int *) * rows); // 行ポインタ配列 for (int i = 0; i < rows; i++) m[i] = malloc(sizeof(int) * cols); // 各行ごとに確保 m[1][2] = 42; // m[i][j] 形式でアクセス可 // 解放は逆順 for (int i = 0; i < rows; i++) free(m[i]); free(m);
C[i][j] = Σ A[i][l] * B[l][j]。#include <stdio.h> #define M 2 #define K 3 #define N 2 void matmul(int A[M][K], int B[K][N], int C[M][N]) { for (int i = 0; i < M; i++) for (int j = 0; j < N; j++) { int sum = 0; for (int l = 0; l < K; l++) sum += A[i][l] * B[l][j]; C[i][j] = sum; } } int main(void) { int A[M][K] = {{1,2,3},{4,5,6}}; int B[K][N] = {{7,8},{9,10},{11,12}}; int C[M][N]; matmul(A, B, C); for (int i = 0; i < M; i++) { for (int j = 0; j < N; j++) printf("%4d", C[i][j]); putchar('\n'); } // 期待: // 58 64 // 139 154 }
ijl の順より ilj の順で内積を累積する方が、Bを行方向に連続アクセスするので高速(行優先の恩恵)。大規模行列では数倍の差が出る。#include <stdio.h> #define W 6 #define H 4 void flip_horizontal(unsigned char img[H][W]) { for (int i = 0; i < H; i++) for (int j = 0; j < W / 2; j++) { unsigned char t = img[i][j]; img[i][j] = img[i][W - 1 - j]; img[i][W - 1 - j] = t; } } int main(void) { unsigned char img[H][W] = { {10,20,30,40,50,60}, {11,21,31,41,51,61}, {12,22,32,42,52,62}, {13,23,33,43,53,63}, }; flip_horizontal(img); for (int i = 0; i < H; i++) { for (int j = 0; j < W; j++) printf("%3d ", img[i][j]); putchar('\n'); } }
unsigned char img[H][W][3] の3次元配列で扱える。上下反転は img[i] ↔ img[H-1-i] を memcpy で行ごとに入れ替える。int A[3][4] の転置 int B[4][3] を求める関数を書け(行と列を入れ替える)。int table[9][9] に九九の表を入れて出力せよ。table[i][j] = (i+1) * (j+1)。int trace(int n, int m[n][n])(VLA引数)を実装せよ。