本檔收錄本單元的練習題、完整解題思路與參考解答。 強烈建議先自己動手做:看完題目就闔上這份檔案,寫出你的版本、編譯、執行、卡住了再回來看「解題思路」; 真的做完了,再對照「參考解答」與「程式碼講解」。每題的完整程式碼也放在 exercises/ 資料夾,可直接編譯:

cc -std=c11 -Wall -Wextra -o /tmp/out exercises/ex1_identity.c && /tmp/out

練習 1:identity —— 設定單位矩陣(難度:基礎)

題目

寫一個函式 void identity(int m[][4], int n):把 n×n 方陣(n <= 4)設為單位矩陣 (對角線為 1、其餘為 0),並在 main 中印出驗證。 測試前先把矩陣填滿垃圾值(如全部 9),證明函式會覆蓋每一格。

輸出範例:

  1  0  0  0
  0  1  0  0
  0  0  1  0
  0  0  0  1

解題思路

  1. 函式參數怎麼寫? 本單元的核心規則:二維陣列傳入函式時,欄數不可省略 (編譯器要靠它計算 m[i][j] = 起點 + (i*欄數 + j)*sizeof(int))。 所以寫 int m[][4](或等價的 int (*m)[4]);列數用參數 n 控制。
  2. 單位矩陣的數學定義直接翻譯成程式:m[i][j] = (i == j) ? 1 : 0; ——雙重迴圈走遍每一格,一行條件運算式搞定。
  3. 為什麼要先填垃圾值再呼叫? 這是寫測試的好習慣: 如果函式只設定對角線而忘了清 0 其他格子,用「全 0 起始」的矩陣測不出這個 bug; 用全 9 起始就會原形畢露。
  4. 欄數用 #define COLS 4 集中管理,函式簽名與 main 的宣告共用同一常數,改一處即可。

參考解答

/*
 * ex1_identity.c
 * --------------
 * 練習 1:identity —— 把方陣設為單位矩陣(難度:基礎)
 *
 * 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex1_identity.c
 * 執行:/tmp/out
 */
#include <stdio.h>

#define COLS 4    /* 函式參數的欄數必須是編譯期已知 */

/*
 * 把 n×n 方陣設成單位矩陣:對角線(i == j)為 1,其餘為 0。
 * 參數寫 int m[][COLS]:欄數不可省略,列數交由 n 控制。
 */
void identity(int m[][COLS], int n)
{
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            m[i][j] = (i == j) ? 1 : 0;
}

void print_matrix(const int m[][COLS], int n)
{
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
            printf("%3d", m[i][j]);
        printf("\n");
    }
}

int main(void)
{
    /* 先填入垃圾值,證明 identity 會覆蓋每一格 */
    int m[COLS][COLS] = {
        { 9, 9, 9, 9 },
        { 9, 9, 9, 9 },
        { 9, 9, 9, 9 },
        { 9, 9, 9, 9 },
    };

    identity(m, COLS);
    printf("4x4 單位矩陣:\n");
    print_matrix(m, COLS);

    return 0;
}

程式碼講解

  • void identity(int m[][COLS], int n):m 實際上是 int (*m)[COLS]—— 「指向 4 個 int 的陣列」的指標。函式內對 m[i][j] 的修改直接作用在呼叫端的矩陣上。
  • (i == j) ? 1 : 0:比 if/else 精簡;也可以先全部清 0、再把對角線設 1,兩種寫法等價。
  • print_matrix 的參數加 const:唯讀函式的自我聲明。 注意 int m[][4] 傳給 const int (*)[4] 參數是安全且合法的。
  • 易錯處:函式參數寫成 int m[][](欄數空白)是編譯錯誤; 寫成 int **m 更是完全不同的型別(指標陣列,記憶體中要有指標表),不能混用。

範例輸出

4x4 單位矩陣:
  1  0  0  0
  0  1  0  0
  0  0  1  0
  0  0  0  1

練習 2:trace —— 對角線元素之和(難度:基礎)

題目

寫一個函式 int trace(const int m[][4], int n):回傳 n×n 方陣對角線元素之和。 用 1..16 填滿的 4×4 矩陣測試(trace 應為 1+6+11+16 = 34), 並額外測「只取左上 2×2 子方陣」的情況(trace 應為 1+6 = 7)。

解題思路

  1. 觀察對角線的規律:m[0][0]、m[1][1]、m[2][2]、m[3][3]—— 列索引與欄索引永遠相等。
  2. 所以不需要雙重迴圈掃全表再判斷 i == j(那是 O(n²)); 一個索引就能直接命中對角線:for (i = 0..n-1) sum += m[i][i];——O(n)。
  3. 參數 n 的妙用:它控制「掃到第幾列」。傳 n = 2 就只加 m[0][0] + m[1][1], 等於取左上 2×2 子方陣的 trace——同一個函式自然支援。
  4. 唯讀函式,參數加 const。

參考解答

/*
 * ex2_trace.c
 * -----------
 * 練習 2:trace —— 方陣對角線元素之和(難度:基礎)
 *
 * 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex2_trace.c
 * 執行:/tmp/out
 */
#include <stdio.h>

#define COLS 4

/*
 * 矩陣的 trace = m[0][0] + m[1][1] + ... + m[n-1][n-1]。
 * 對角線元素的特徵是「列索引 == 欄索引」,
 * 所以一個迴圈、一個索引 i 就夠了,不需要雙重迴圈。
 */
int trace(const int m[][COLS], int n)
{
    int sum = 0;
    for (int i = 0; i < n; i++)
        sum += m[i][i];
    return sum;
}

int main(void)
{
    int m[4][COLS] = {
        {  1,  2,  3,  4 },
        {  5,  6,  7,  8 },
        {  9, 10, 11, 12 },
        { 13, 14, 15, 16 },
    };

    printf("矩陣:\n");
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < COLS; j++)
            printf("%4d", m[i][j]);
        printf("\n");
    }

    /* 1 + 6 + 11 + 16 = 34 */
    printf("trace = %d\n", trace(m, 4));

    /* 只取左上 2x2 子方陣的 trace:1 + 6 = 7 */
    printf("左上 2x2 的 trace = %d\n", trace(m, 2));

    return 0;
}

程式碼講解

  • sum += m[i][i]:對角線的本質就是「兩個索引相同」。 底層計算是 起點 + (i * 4 + i) * sizeof(int),也就是每次跳 5 個 int—— 這解釋了為什麼 row-major 佈局下對角線元素間隔固定。
  • trace(m, 2) 只掃前兩列的前兩欄:因為欄數(實際佈局)仍然是 4, 位址計算照舊,取的是左上角子方陣,這是欄數固定、列數彈性的直接效果。
  • 易錯處:想用雙重迴圈加 if (i == j) 也對,但多繞了一圈; 更糟的錯誤是把條件寫成指派 if (i = j)——開 -Wall 會警告。

範例輸出

矩陣:
   1   2   3   4
   5   6   7   8
   9  10  11  12
  13  14  15  16
trace = 34
左上 2x2 的 trace = 7

練習 3:weekday_name —— 指標陣列查表(難度:基礎)

題目

仿照 K&R §5.8 的 month_name,寫函式 const char *weekday_name(int d): 回傳星期名稱(d = 1 是 "Monday"……d = 7 是 "Sunday")。 非法輸入(d < 1 或 d > 7)回傳守衛字串 "Illegal weekday"。 測試 1..7 全部值與至少三個非法值(0、8、-3)。

解題思路

  1. 為什麼用指標陣列? 七個名稱長短不一("Monday" 6 字、"Wednesday" 9 字), 用二維字元陣列 char name[8][20] 會浪費空間; 指標陣列讓每個字串只占實際需要的空間:
name[0] --> "Illegal weekday\0"
name[1] --> "Monday\0"
name[2] --> "Tuesday\0"
   ...
name[7] --> "Sunday\0"
  1. 守衛值技巧(K&R 的招式):把「錯誤訊息」放在表的第 0 格, 讓合法輸入 1..7 直接當索引用(不必 d-1 換算),非法輸入統一導向 name[0]。 一行完成:return (d < 1 || d > 7) ? name[0] : name[d];
  2. 為什麼要 static? 沒有 static 的區域陣列在函式返回後就消失, 回傳指向它的指標就是懸空指標。static 讓陣列活到程式結束,回傳其中的指標才安全。 (字串字面值本身永遠有效,但指標陣列這 8 個指標需要 static 保命。)
  3. const 的講究:字面值不可修改,所以表宣告成 const char *name[]、 回傳型別用 const char *——讓編譯器擋下呼叫端的誤寫。 (K&R 原文寫 char *,是當年還沒有 const 的歷史包袱。)

參考解答

/*
 * ex3_weekday_name.c
 * ------------------
 * 練習 3:weekday_name —— 仿 K&R month_name 的指標陣列查表(難度:基礎)
 *
 * 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex3_weekday_name.c
 * 執行:/tmp/out
 */
#include <stdio.h>

/*
 * 回傳星期名稱:d = 1(Monday)到 7(Sunday)。
 * 非法輸入回傳守衛字串 name[0](K&R §5.8 month_name 的招式)。
 * static 讓指標陣列在函式返回後仍存在,回傳其中的指標才安全。
 */
const char *weekday_name(int d)
{
    static const char *name[] = {
        "Illegal weekday",
        "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday", "Sunday"
    };

    return (d < 1 || d > 7) ? name[0] : name[d];
}

int main(void)
{
    /* 正常範圍 1..7 */
    for (int d = 1; d <= 7; d++)
        printf("weekday_name(%d) = %s\n", d, weekday_name(d));

    /* 非法輸入:守衛值出場 */
    printf("weekday_name(0)  = %s\n", weekday_name(0));
    printf("weekday_name(8)  = %s\n", weekday_name(8));
    printf("weekday_name(-3) = %s\n", weekday_name(-3));

    return 0;
}

程式碼講解

  • 表共 8 個元素:索引 0 是守衛、1..7 是星期一到星期日,索引即語意,不用換算。
  • 範圍檢查 (d < 1 || d > 7) 必須在取索引之前:name[-3]、name[8] 都是越界讀。 「先驗證、後查表」與指標的「先檢查、後解參考」是同一種紀律。
  • static const char *name[]:static 管生命週期(活到程式結束)、 const 管唯讀(指向的字面值不可改)。兩個關鍵字各司其職。
  • 易錯處:忘記守衛直接 return name[d];——非法輸入變成未定義行為; 或忘記 static——雖然「字面值指標」剛好仍有效(字面值本身不死), 但這是僥倖;換成回傳陣列元素的位址就會出事,養成加 static 的習慣。

範例輸出

weekday_name(1) = Monday
weekday_name(2) = Tuesday
weekday_name(3) = Wednesday
weekday_name(4) = Thursday
weekday_name(5) = Friday
weekday_name(6) = Saturday
weekday_name(7) = Sunday
weekday_name(0)  = Illegal weekday
weekday_name(8)  = Illegal weekday
weekday_name(-3) = Illegal weekday

練習 4:sort_strings —— 只交換指標的字串排序(難度:進階)

題目

寫函式 void sort_strings(const char *a[], int n): 用 strcmp 比較字串內容,把字串指標陣列按字典序排序。 限制:只能交換指標,不得搬動或複製字串本身。 用 { "python", "c", "rust", "go", "javascript", "ada" } 測試。

解題思路

  1. 這題到底在排什麼? 陣列裡放的是 6 個指標,字串本體散落在唯讀區。 「排序」只需要把指標重新洗牌,讓 a[0] 指向字典序最小的字串:
排序前:  a[0] --> "python"      排序後:  a[0] --> "ada"
         a[1] --> "c"                    a[1] --> "c"
         a[2] --> "rust"                 a[2] --> "go"
         ...                             ...
(字串本體一個 byte 都沒動,動的只是 6 個 8-byte 的指標)

這就是 K&R 排序文字行的核心:交換兩「列」的成本從搬整串字元降到搬一個指標。 2. 比較用什麼? strcmp(a[j], a[min]) < 0 表示 a[j] 字典序較小。 千萬不能寫 a[j] < a[min]——那是在比記憶體位址,毫無意義。 3. 排序演算法:選擇排序最直觀——每一輪從未排序區找最小者,換到最前面。 n 只有 6,O(n²) 完全夠用;重點在「交換的單位是指標」。 4. 交換三行:臨時變數的型別是 const char *(跟元素同型別)。

參考解答

/*
 * ex4_sort_strings.c
 * ------------------
 * 練習 4:sort_strings —— 只交換指標,把字串陣列按字典序排序(難度:進階)
 *
 * 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex4_sort_strings.c
 * 執行:/tmp/out
 */
#include <stdio.h>
#include <string.h>

/*
 * 選擇排序字串指標陣列。
 * 關鍵:字串本身一個字元都沒搬——搬動的只是「指標」(每個 8 bytes)。
 * 這正是 K&R §5.6 排序文字行的核心技巧。
 */
void sort_strings(const char *a[], int n)
{
    for (int i = 0; i < n - 1; i++) {
        int min = i;                       /* 找出 [i, n) 中字典序最小者 */
        for (int j = i + 1; j < n; j++)
            if (strcmp(a[j], a[min]) < 0)
                min = j;

        if (min != i) {                    /* 只交換兩個指標 */
            const char *tmp = a[i];
            a[i] = a[min];
            a[min] = tmp;
        }
    }
}

static void print_all(const char *title, const char *a[], int n)
{
    printf("%s\n", title);
    for (int i = 0; i < n; i++)
        printf("  [%d] %s\n", i, a[i]);
}

int main(void)
{
    const char *langs[] = {
        "python", "c", "rust", "go", "javascript", "ada"
    };
    int n = (int)(sizeof langs / sizeof langs[0]);

    print_all("排序前:", langs, n);
    sort_strings(langs, n);
    print_all("排序後:", langs, n);

    return 0;
}

程式碼講解

  • const char *a[]:「指向 const char 的指標」的陣列——字串內容唯讀, 但指標本身可以重新指派,所以 a[i] = a[min]; 完全合法。 這正是表格第 5 節說的「指標陣列的 a[1] = a[2] 合法、二維陣列不行」。
  • strcmp 回傳 <0 / 0 / >0 三態,判斷字典序用 < 0,不是把它當布林值用。
  • 交換的三行裡 tmp 型別必須是 const char *;宣告成 char * 會有 「丟掉 const」的警告。
  • 傳入函式後 a 退化成 const char **(指標的指標), 但 a[i] 的用法完全不變——這是指標陣列與 char ** 可互換的體現。
  • 易錯處:用 ==/< 直接比指標(比到位址);或想用 strcpy 搬字串內容—— 不但違反題目限制,字面值唯讀,一搬就 crash。

範例輸出

排序前:
  [0] python
  [1] c
  [2] rust
  [3] go
  [4] javascript
  [5] ada
排序後:
  [0] ada
  [1] c
  [2] go
  [3] javascript
  [4] python
  [5] rust

練習 5:month_day —— day_of_year 的反函式(難度:進階)

題目

(K&R §5.7)實作 void month_day(int year, int yearday, int *pmonth, int *pday): 給定年份與「年中的第幾天」,透過指標參數回傳月與日。 例如 2025 年第 60 天是 3 月 1 日;閏年 2024 年第 60 天是 2 月 29 日。 請同時實作正向的 day_of_year 做來回驗證(如 2024/8/15 → 第 228 天 → 還原回 8/15)。

解題思路

  1. 資料表示:沿用 K&R 的二維天數表——第 0 列平年、第 1 列閏年, [0] 欄不用(讓月份 1..12 直接當索引):
static const int daytab[2][13] = {
    { 0, 31, 28, 31, ... },   /* 平年 */
    { 0, 31, 29, 31, ... },   /* 閏年 */
};

「用一個 0/1 的 leap 當列索引」取代滿地的 if (閏年),是查表法的精髓。 2. 反函式的邏輯:yearday 從 1 月開始一個月一個月地「扣」: - 第 60 天(平年):扣 1 月的 31 → 剩 29;29 <= 2 月的 28?否,再扣 → 剩 1; 1 <= 3 月的 31?是 → 停。答案:3 月 1 日。 - 迴圈條件寫成 yearday > daytab[leap][i]:還扣得動就繼續。 3. 輸出兩個結果:return 只能回傳一個值,所以月、日用指標參數 *pmonth = i; *pday = yearday; 填回呼叫端(複習第 01 單元的 min_max 模式)。 4. 閏年公式:能被 4 整除且不被 100 整除,或能被 400 整除。 5. 驗證策略:正反函式互轉應該還原(round-trip test)—— day_of_year(2024,8,15) = 228,month_day(2024, 228) 應回到 8 月 15 日。

參考解答

/*
 * ex5_month_day.c
 * ---------------
 * 練習 5:month_day —— day_of_year 的反函式(難度:進階)
 * 出處:K&R §5.7 的日期轉換範例
 *
 * 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex5_month_day.c
 * 執行:/tmp/out
 */
#include <stdio.h>

/* K&R §5.7 的閏年天數表:第 0 列平年、第 1 列閏年([0] 欄不用) */
static const int daytab[2][13] = {
    { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
};

/* 閏年判斷:能被 4 整除且不被 100 整除,或能被 400 整除 */
static int is_leap(int year)
{
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

/* 正向:年 + 月 + 日 -> 年中的第幾天(對照用) */
int day_of_year(int year, int month, int day)
{
    int leap = is_leap(year);
    for (int i = 1; i < month; i++)
        day += daytab[leap][i];
    return day;
}

/*
 * 反向:年 + 年中的第幾天 -> 月與日(用指標參數回傳兩個結果)。
 * 想法:從 1 月開始,一個月一個月地「扣掉」整月的天數,
 * 扣不動了,剩下的就是「日」,扣到的月份就是「月」。
 */
void month_day(int year, int yearday, int *pmonth, int *pday)
{
    int leap = is_leap(year);
    int i;

    for (i = 1; yearday > daytab[leap][i]; i++)
        yearday -= daytab[leap][i];

    *pmonth = i;         /* 透過指標把兩個結果填回呼叫端 */
    *pday = yearday;
}

int main(void)
{
    int month, day;

    /* 平年 2025:第 60 天應是 3 月 1 日(31+28=59 天後) */
    month_day(2025, 60, &month, &day);
    printf("2025 年第 60 天  = %d 月 %d 日\n", month, day);

    /* 閏年 2024:第 60 天應是 2 月 29 日 */
    month_day(2024, 60, &month, &day);
    printf("2024 年第 60 天  = %d 月 %d 日\n", month, day);

    /* 邊界:第 1 天與最後一天 */
    month_day(2025, 1, &month, &day);
    printf("2025 年第 1 天   = %d 月 %d 日\n", month, day);
    month_day(2025, 365, &month, &day);
    printf("2025 年第 365 天 = %d 月 %d 日\n", month, day);
    month_day(2024, 366, &month, &day);
    printf("2024 年第 366 天 = %d 月 %d 日\n", month, day);

    /* 互為反函式的驗證:來回轉換應還原 */
    int yd = day_of_year(2024, 8, 15);
    month_day(2024, yd, &month, &day);
    printf("2024/8/15 -> 第 %d 天 -> %d 月 %d 日(還原成功)\n",
           yd, month, day);

    return 0;
}

程式碼講解

  • daytab[2][13]:二維陣列當「雙版本查找表」。leap 是 0 或 1, 直接當第一維索引——沒有半個 if (閏月) 散落在邏輯裡。
  • month_day 的迴圈變數 i 宣告在迴圈外面:迴圈結束後還要用它 (它就是答案的「月」),這是少數不能把 int i 塞進 for 括號的情況。
  • 迴圈條件 yearday > daytab[leap][i]:注意是 > 不是 >=。 若第 31 天,31 > 31 不成立 → 停在 1 月 31 日,正確; 用 >= 會多扣一個月,變成 2 月 0 日。差一錯誤(off-by-one)是日期程式的頭號殺手。
  • 邊界測試涵蓋第 1 天、平年第 365 天、閏年第 366 天,加上 round-trip 驗證, 對這類轉換函式是必要的最小測試集。

範例輸出

2025 年第 60 天  = 3 月 1 日
2024 年第 60 天  = 2 月 29 日
2025 年第 1 天   = 1 月 1 日
2025 年第 365 天 = 12 月 31 日
2024 年第 366 天 = 12 月 31 日
2024/8/15 -> 第 228 天 -> 8 月 15 日(還原成功)

練習 6:matmul —— 3×3 矩陣乘法(難度:挑戰)

題目

寫一個矩陣乘法函式:

void matmul(const int a[][3], const int b[][3], int out[][3]);

計算 out = a × b(3×3 矩陣相乘)並印出結果。 額外驗證:任何矩陣乘上單位矩陣應等於自己(A × I = A)。

解題思路

  1. 先回想數學定義:結果矩陣的第 i 列第 j 欄, 是 A 的第 i 列與 B 的第 j 欄的內積:
out[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j]
          = Σk a[i][k] * b[k][j]

        A 的第 i 列 ────────┐
   [ a a a ]   [ b . . ]     │
   [ . . . ] × [ b . . ]  =  ▼ out[i][j]
   [ . . . ]   [ b . . ]
                 ▲
                 B 的第 j 欄
  1. 翻譯成三層迴圈:外兩層 i、j 決定「算哪一格」, 內層 k 做內積累加。累加器 sum 每格歸零一次——放在 j 迴圈裡面、k 迴圈外面。
  2. 參數設計:輸入矩陣加 const(唯讀),輸出矩陣不加; 三個參數都是 [][3] 形式——欄數 3 讓編譯器能算位址。
  3. 驗證手段:矩陣乘法容易寫錯索引(b[k][j] 寫成 b[j][k] 就變成乘了轉置)。 用「單位矩陣測試」快速抓錯:A × I 不等於 A 就一定有 bug。

參考解答

/*
 * ex6_matmul.c
 * ------------
 * 練習 6:matmul —— 3×3 矩陣乘法(難度:挑戰)
 *
 * 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex6_matmul.c
 * 執行:/tmp/out
 */
#include <stdio.h>

#define N 3

/*
 * out = a × b。
 * 定義:out[i][j] = Σk a[i][k] * b[k][j]
 * (out 的第 i 列第 j 欄 = a 的第 i 列「點乘」b 的第 j 欄)
 */
void matmul(const int a[][N], const int b[][N], int out[][N])
{
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            int sum = 0;
            for (int k = 0; k < N; k++)
                sum += a[i][k] * b[k][j];
            out[i][j] = sum;
        }
    }
}

static void print_matrix(const char *name, const int m[][N])
{
    printf("%s =\n", name);
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++)
            printf("%5d", m[i][j]);
        printf("\n");
    }
}

int main(void)
{
    int a[N][N] = {
        { 1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 },
    };
    int b[N][N] = {
        { 9, 8, 7 },
        { 6, 5, 4 },
        { 3, 2, 1 },
    };
    int identity[N][N] = {
        { 1, 0, 0 },
        { 0, 1, 0 },
        { 0, 0, 1 },
    };
    int result[N][N];

    print_matrix("A", a);
    print_matrix("B", b);

    matmul(a, b, result);
    print_matrix("A x B", result);

    /* 驗證:任何矩陣乘上單位矩陣應等於自己 */
    matmul(a, identity, result);
    print_matrix("A x I(應等於 A)", result);

    return 0;
}

程式碼講解

  • 三層迴圈中索引的角色:i 選 A 的列、j 選 B 的欄、k 同步走過 「A 的欄」與「B 的列」——a[i][k] * b[k][j] 的 k 出現兩次且位置相反,是核心。
  • int sum = 0; 宣告在 j 迴圈內:每一格獨立累加。 放錯位置(例如放到 i 層外)會讓所有格子的值疊在一起,是常見 bug。
  • 手驗一格:(A×B)[0][0] = 1*9 + 2*6 + 3*3 = 30 ✓(與輸出一致)。
  • 限制:matmul(a, a, a) 這種「輸出與輸入同一個矩陣」的呼叫是錯的—— 寫 out[i][j] 時會覆寫還沒讀到的輸入。需要就地相乘就得先複製到暫存矩陣。
  • 想支援任意大小可用 C99 變長陣列參數: void matmul(int n, const int a[n][n], const int b[n][n], int out[n][n])。

範例輸出

A =
    1    2    3
    4    5    6
    7    8    9
B =
    9    8    7
    6    5    4
    3    2    1
A x B =
   30   24   18
   84   69   54
  138  114   90
A x I(應等於 A) =
    1    2    3
    4    5    6
    7    8    9

常見錯誤解法對照

錯誤寫法 症狀 正確做法
函式參數寫 int m[][] 編譯錯誤:欄數必須給 int m[][4] 或 int (*m)[4]
把二維陣列傳給 int ** 參數 編譯警告 + 執行期錯亂(沒有指標表) 參數型別用 int (*)[COLS]
sort_strings 用 a[j] < a[min] 比較 比的是位址不是內容,順序隨機 strcmp(a[j], a[min]) < 0
weekday_name 不檢查範圍直接 name[d] 越界讀,非法輸入時未定義行為 先驗證 (d < 1 || d > 7) 再查表
month_day 迴圈條件用 >= 差一錯誤:月底那天被推到下個月 yearday > daytab[leap][i]
matmul 的 sum 放錯層 各格的值互相污染 sum 在 j 迴圈內歸零