本檔收錄本單元的練習題、完整解題思路與參考解答。
強烈建議先自己動手做:看完題目就闔上這份檔案,寫出你的版本、編譯、執行、卡住了再回來看「解題思路」;
真的做完了,再對照「參考解答」與「程式碼講解」。每題的完整程式碼也放在 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
解題思路
- 函式參數怎麼寫? 本單元的核心規則:二維陣列傳入函式時,欄數不可省略
(編譯器要靠它計算
m[i][j] = 起點 + (i*欄數 + j)*sizeof(int))。 所以寫int m[][4](或等價的int (*m)[4]);列數用參數n控制。 - 單位矩陣的數學定義直接翻譯成程式:
m[i][j] = (i == j) ? 1 : 0;——雙重迴圈走遍每一格,一行條件運算式搞定。 - 為什麼要先填垃圾值再呼叫? 這是寫測試的好習慣: 如果函式只設定對角線而忘了清 0 其他格子,用「全 0 起始」的矩陣測不出這個 bug; 用全 9 起始就會原形畢露。
- 欄數用
#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)。
解題思路
- 觀察對角線的規律:
m[0][0]、m[1][1]、m[2][2]、m[3][3]—— 列索引與欄索引永遠相等。 - 所以不需要雙重迴圈掃全表再判斷
i == j(那是 O(n²)); 一個索引就能直接命中對角線:for (i = 0..n-1) sum += m[i][i];——O(n)。 - 參數
n的妙用:它控制「掃到第幾列」。傳n = 2就只加m[0][0] + m[1][1], 等於取左上 2×2 子方陣的 trace——同一個函式自然支援。 - 唯讀函式,參數加
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)。
解題思路
- 為什麼用指標陣列? 七個名稱長短不一(
"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"
- 守衛值技巧(K&R 的招式):把「錯誤訊息」放在表的第 0 格,
讓合法輸入
1..7直接當索引用(不必d-1換算),非法輸入統一導向name[0]。 一行完成:return (d < 1 || d > 7) ? name[0] : name[d]; - 為什麼要
static? 沒有static的區域陣列在函式返回後就消失, 回傳指向它的指標就是懸空指標。static讓陣列活到程式結束,回傳其中的指標才安全。 (字串字面值本身永遠有效,但指標陣列這 8 個指標需要static保命。) 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" } 測試。
解題思路
- 這題到底在排什麼? 陣列裡放的是 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)。
解題思路
- 資料表示:沿用 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)。
解題思路
- 先回想數學定義:結果矩陣的第
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 欄
- 翻譯成三層迴圈:外兩層
i、j決定「算哪一格」, 內層k做內積累加。累加器sum每格歸零一次——放在j迴圈裡面、k迴圈外面。 - 參數設計:輸入矩陣加
const(唯讀),輸出矩陣不加; 三個參數都是[][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 迴圈內歸零 |