本檔收錄本單元的練習題、完整解題思路與參考解答。
強烈建議先自己動手做:看完題目就闔上這份檔案,寫出你的版本、編譯、執行、卡住了再回來看「解題思路」;
真的做完了,再對照「參考解答」與「程式碼講解」。每題的完整程式碼也放在 exercises/ 資料夾,可直接編譯:
cc -std=c11 -Wall -Wextra -o /tmp/out exercises/ex1_average_ptr.c && /tmp/out
練習 1:雙指標迴圈計算平均值(難度:基礎)
題目
寫一個函式 double average(const int *a, int n),計算 int 陣列的平均值。
限制:全程不得使用 [] 下標,必須用「雙指標版」迴圈
(讓指標 p 從 a 走到尾後指標 a + n)。
用 { 88, 72, 95, 60, 79 } 測試,另外測「單一元素」的邊界情況。
解題思路
- 回想三種等價的走訪寫法(單元講義第 3 節),本題指定最後一種——雙指標版:
a[0] a[1] a[2] a[3] a[4] (尾後)
+-------+-------+-------+-------+-------+ - - -
| 88 | 72 | 95 | 60 | 79 |
+-------+-------+-------+-------+-------+ - - -
^ ^
p 從這裡開始 a + n:迴圈終點
(可比較、不可解參考)
for (const int *p = a; p < a + n; p++) sum += *p;
—— p < a + n 成立時 p 一定指向合法元素,所以 *p 安全。
- 累加型別的選擇:
int加總大陣列可能溢位,用long累加穩妥。 - 平均值的型別陷阱:
sum / n是整數除法,小數會被截掉! 必須先把其中一邊轉成double:(double)sum / n。 const int *a:只讀不寫,讓編譯器與讀者都放心。
參考解答
/*
* ex1_average_ptr.c
* -----------------
* 練習 1:用「雙指標版」迴圈計算陣列平均值,全程不用 [] 下標(難度:基礎)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex1_average_ptr.c
* 執行:/tmp/out
*/
#include <stdio.h>
/*
* 雙指標版走訪:p 從陣列開頭走到「尾後一格」(a + n)。
* 尾後指標只能參與比較,不可解參考。
*/
double average(const int *a, int n)
{
long sum = 0; /* 用 long 累加,避免大陣列溢位 */
for (const int *p = a; p < a + n; p++) /* p 一路前進到尾後為止 */
sum += *p;
return (double)sum / n; /* 轉成 double 再除,保留小數 */
}
int main(void)
{
int scores[] = { 88, 72, 95, 60, 79 };
int n = (int)(sizeof scores / sizeof scores[0]);
/* 印出內容也用指標走訪,示範完全不用下標 */
printf("資料:");
for (const int *p = scores; p < scores + n; p++)
printf("%d ", *p);
printf("\n");
printf("平均值 = %.2f\n", average(scores, n));
/* 邊界:單一元素 */
int one[] = { 42 };
printf("單一元素的平均值 = %.2f\n", average(one, 1));
return 0;
}
程式碼講解
p < a + n:迴圈條件用「還沒走到尾後」表達,是 C 最慣用的指標迴圈邊界。 注意a + n這個位址保證可以拿來比較(C 標準明文允許),但解參考它是未定義行為。sum += *p:讀出p目前指向的元素。p++每次前進「一個 int」(編譯器自動乘sizeof(int))。(double)sum / n:轉型寫在除法之前。若寫成(double)(sum / n), 整數除法已經先發生,轉型救不回小數——這是本題最典型的錯誤。- 88+72+95+60+79 = 394,394 / 5 = 78.8,與輸出一致。
範例輸出
資料:88 72 95 60 79
平均值 = 78.80
單一元素的平均值 = 42.00
練習 2:my_strlen —— 下標版與指標相減版(難度:基礎)
題目
實作 size_t my_strlen(const char *s),回傳字串長度(不含 '\0'),分別寫出兩個版本:
- 下標版
my_strlen_idx:用索引i數到'\0'; - 指標相減版
my_strlen_ptr:讓指標p走到'\0',回傳p - s。
用 "hello"、空字串 ""、單字元 "a"、"now is the time" 驗證兩版結果一致。
解題思路
- 字串的本質:以
'\0'結尾的字元陣列。「長度」就是'\0'之前的字元個數。 - 下標版很直觀:從
i = 0開始,s[i]不是'\0'就i++; 迴圈結束時i恰好是長度(s[i]正停在'\0'上)。 - 指標相減版是 K&R §5.4 展示「位址算術」的經典:
- 記下起點
const char *p = s;-p一路前進到'\0'; - 兩個指向同一陣列的指標相減,得到相隔的元素個數:p - s就是長度。
s p(停在 '\0')
v v
+---+---+---+---+---+------+
| h | e | l | l | o | '\0' |
+---+---+---+---+---+------+
p - s = 5 = 字串長度
- 回傳型別:長度不會是負的,標準庫用
size_t(無號),我們照辦。 指標相減的結果型別是ptrdiff_t(有號),轉成size_t回傳時加上明確轉型, 在-Wextra下才不會有符號轉換的疑慮。
參考解答
/*
* ex2_my_strlen.c
* ---------------
* 練習 2:實作 my_strlen —— 下標版與「指標相減」版(難度:基礎)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex2_my_strlen.c
* 執行:/tmp/out
*/
#include <stdio.h>
/* 下標版:數到 '\0' 為止 */
size_t my_strlen_idx(const char *s)
{
size_t i = 0;
while (s[i] != '\0')
i++;
return i;
}
/*
* 指標相減版(K&R §5.4 的寫法):
* 讓 p 走到 '\0',p - s 就是中間隔了幾個字元。
*/
size_t my_strlen_ptr(const char *s)
{
const char *p = s;
while (*p != '\0')
p++;
return (size_t)(p - s); /* 指標相減得到元素個數(ptrdiff_t) */
}
int main(void)
{
const char *tests[] = { "hello", "", "a", "now is the time" };
int n = (int)(sizeof tests / sizeof tests[0]);
for (int i = 0; i < n; i++) {
printf("\"%s\":下標版 = %zu,指標版 = %zu\n",
tests[i],
my_strlen_idx(tests[i]),
my_strlen_ptr(tests[i]));
}
return 0;
}
程式碼講解
- 兩版的迴圈條件都是「還沒遇到
'\0'」。空字串第一個字元就是'\0', 迴圈一次都不跑,回傳 0——邊界天然正確。 (size_t)(p - s):p - s的型別是ptrdiff_t(有號整數)。 這裡p >= s恆成立、結果不會是負的,轉型安全。- 印
size_t用格式%zu(用%d是型別不符,-Wall會警告)。 const char *tests[]是「指標陣列」——四個指標各指向一個字串字面值, 順便預習了下一單元的主題。- 易錯處:指標版若忘了先存起點(直接動
s而沒有p),最後就沒有東西可以相減。 參數s是複本,動它不影響呼叫端,但你得自己留一份起點。
範例輸出
"hello":下標版 = 5,指標版 = 5
"":下標版 = 0,指標版 = 0
"a":下標版 = 1,指標版 = 1
"now is the time":下標版 = 15,指標版 = 15
練習 3:my_strchr —— 找字元第一次出現的位置(難度:基礎)
題目
實作 char *my_strchr(const char *s, int c):
回傳字元 c 在字串 s 中第一次出現位置的指標;找不到回傳 NULL。
比照標準庫 strchr 的規格:搜尋 '\0' 也要能運作(回傳指向結尾 '\0' 的指標)。
用 "hello, world" 測試:找 'o'、找 'l'(出現多次)、找 'z'(不存在)、找 '\0'。
解題思路
- 回傳值設計:這題的重點是「回傳指標而不是索引」。
回傳指標的好處:呼叫端拿到後可以直接當「子字串」用(從該處印到結尾),
也可以用
p - s換算回索引——資訊沒有損失。 - 主迴圈:讓
s逐字前進,每一步比較*s == c;找到就回傳目前的s。 走到'\0'都沒找到,回傳NULL。 - 特例——搜尋
'\0':標準規定strchr(s, '\0')要回傳指向結尾'\0'的指標。 主迴圈在*s == '\0'時就停了,比不到結尾那格,所以迴圈後補一個判斷。 - 為什麼參數是
int c卻要轉(char)c比較? 標準庫介面就是int(歷史因素),實作時轉回char再比,語意才正確。 (char *)s的轉型:輸入是const char *,回傳卻是char *—— 這是照抄標準庫的介面(讓傳入非 const 字串的呼叫端拿回可寫指標)。 自己設計新介面時,更乾淨的做法是回傳const char *。
參考解答
/*
* ex3_my_strchr.c
* ---------------
* 練習 3:實作 my_strchr —— 找字元第一次出現的位置(難度:基礎)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex3_my_strchr.c
* 執行:/tmp/out
*/
#include <stdio.h>
#include <stddef.h>
/*
* 回傳 c 在 s 中第一次出現位置的指標;找不到回傳 NULL。
* 仿照標準庫介面:c 以 int 傳入、回傳非 const 的 char *
* (標準庫如此設計是歷史包袱,這裡照規格實作)。
* 注意:'\0' 也算可搜尋的字元——strchr(s, '\0') 回傳指向結尾的指標。
*/
char *my_strchr(const char *s, int c)
{
while (*s != '\0') {
if (*s == (char)c)
return (char *)s; /* 找到:回傳目前位置 */
s++;
}
if ((char)c == '\0') /* 特例:搜尋 '\0' 本身 */
return (char *)s;
return NULL; /* 走完整個字串都沒找到 */
}
int main(void)
{
const char *text = "hello, world";
/* 找得到的情況:印出位置(用指標相減算索引) */
char *p = my_strchr(text, 'o');
if (p != NULL)
printf("'o' 第一次出現在索引 %td,之後的內容:\"%s\"\n",
p - text, p);
/* 'l' 出現多次,只回報第一次 */
p = my_strchr(text, 'l');
printf("'l' 第一次出現在索引 %td\n", p - text);
/* 找不到的情況 */
p = my_strchr(text, 'z');
printf("找 'z' 的結果:%s\n", (p == NULL) ? "NULL(不存在)" : "找到了");
/* 搜尋 '\0':回傳指向字串結尾的指標 */
p = my_strchr(text, '\0');
printf("找 '\\0':回傳位置的索引 = %td(= 字串長度)\n", p - text);
return 0;
}
程式碼講解
- 找到就立刻 return:「第一次出現」的語意由「從頭掃、先到先贏」自動保證。
p - text把指標換算回索引;印ptrdiff_t用格式%td。- 找
'\0'回傳的索引恰好等於字串長度(12),這其實是strchr(s, 0)求長度的老技巧。 - 呼叫端必須檢查 NULL 再使用——直接
printf("%s", my_strchr(text, 'z'))就是對 NULL 解參考,正好複習上一單元的教訓。 - 易錯處:把迴圈寫成
while (*s != c)直接衝——若c不存在,s會越過'\0'繼續狂奔(越界讀取)。邊界條件必須是'\0',比對放在迴圈裡面。
範例輸出
'o' 第一次出現在索引 4,之後的內容:"o, world"
'l' 第一次出現在索引 2
找 'z' 的結果:NULL(不存在)
找 '\0':回傳位置的索引 = 12(= 字串長度)
練習 4:reverse_in_place —— 頭尾雙指標就地反轉(難度:進階)
題目
實作 void reverse_in_place(int *a, int n):用「頭尾雙指標」互換元素,把陣列就地反轉
(不得使用第二個陣列)。
分別用偶數個元素、奇數個元素、單一元素的陣列測試。
解題思路
- 「就地」的意思:只能在原陣列裡搬動,不准開新陣列。 核心觀察:反轉就是「第 1 個和最後 1 個交換、第 2 個和倒數第 2 個交換……」。
- 雙指標設計:
-
lo = a:指向最前面; -hi = a + n - 1:指向最後一個元素(注意不是尾後a + n,那格不能碰!); - 每一輪:交換*lo與*hi,然後lo++、hi--,兩端向中間夾攻。 - 什麼時候停?
lo < hi不再成立時:
輪次 0: lo hi
v v
[ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
輪次 1: lo hi
v v
[ 6 ][ 2 ][ 3 ][ 4 ][ 5 ][ 1 ]
輪次 2: lo hi
v v
[ 6 ][ 5 ][ 3 ][ 4 ][ 2 ][ 1 ]
交換後 lo++/hi-- 交錯(lo > hi)→ 停止
- 偶數個元素:
lo與hi交錯後停; - 奇數個元素:
lo == hi(都停在正中央)時停——中間那個元素不需要動; - 單一元素:一開始就
lo == hi,一次都不交換,正確。 4. 交換用臨時變數三步驟即可,不必再叫用 swap 函式(當然叫也可以)。
參考解答
/*
* ex4_reverse_in_place.c
* ----------------------
* 練習 4:reverse_in_place —— 頭尾雙指標就地反轉陣列(難度:進階)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex4_reverse_in_place.c
* 執行:/tmp/out
*/
#include <stdio.h>
/*
* 頭尾雙指標:lo 從最前面、hi 從最後面往中間夾,
* 每一步交換 *lo 與 *hi,直到兩指標相遇(或交錯)為止。
*/
void reverse_in_place(int *a, int n)
{
int *lo = a; /* 指向第一個元素 */
int *hi = a + n - 1; /* 指向最後一個元素(不是尾後!) */
while (lo < hi) {
int tmp = *lo;
*lo = *hi;
*hi = tmp;
lo++; /* 兩端同時往中間走 */
hi--;
}
}
static void print_array(const char *label, const int *a, int n)
{
printf("%s", label);
for (const int *p = a; p < a + n; p++)
printf("%d ", *p);
printf("\n");
}
int main(void)
{
int even[] = { 1, 2, 3, 4, 5, 6 }; /* 偶數個元素 */
int odd[] = { 10, 20, 30, 40, 50 }; /* 奇數個元素 */
int one[] = { 99 }; /* 單一元素 */
print_array("偶數個反轉前:", even, 6);
reverse_in_place(even, 6);
print_array("偶數個反轉後:", even, 6);
print_array("奇數個反轉前:", odd, 5);
reverse_in_place(odd, 5);
print_array("奇數個反轉後:", odd, 5);
reverse_in_place(one, 1);
print_array("單一元素反轉後:", one, 1);
return 0;
}
程式碼講解
hi = a + n - 1:本題最容易寫錯的一行。寫成a + n會從尾後那格開始交換—— 越界寫入,是嚴重錯誤(ASan 能抓到)。- 迴圈條件
lo < hi同時涵蓋「相遇」(奇數個)與「交錯」(偶數個)兩種結束情況; 寫成lo != hi在偶數個元素時會永遠不相等而衝過頭。 - 函式收到
int *a就能修改呼叫端的陣列——因為傳入的是首元素位址, 這正是「陣列傳入函式即指標」的直接應用。 - 交換次數只有 n/2 次,每個元素恰好被搬一次,效率是最優的 O(n)。
範例輸出
偶數個反轉前:1 2 3 4 5 6
偶數個反轉後:6 5 4 3 2 1
奇數個反轉前:10 20 30 40 50
奇數個反轉後:50 40 30 20 10
單一元素反轉後:99
練習 5:my_strend —— 字串 t 是否在 s 的尾端(難度:進階)
題目
(K&R 習題 5-4)實作 int my_strend(const char *s, const char *t):
若字串 t 出現在字串 s 的尾端回傳 1,否則回傳 0。
例如 my_strend("homework", "work") 回傳 1;my_strend("homework", "home") 回傳 0。
注意邊界:t 比 s 長、t 為空字串、s 與 t 完全相等。
解題思路
- 換個方向想:「出現在尾端」代表兩個字串的結尾對齊後完全重合。 與其從前面對齊再算偏移,不如從後面往前比:
s = "homework" t = "work"
ps pt
v v
h o m e w o r k w o r k
^ ^
先把兩個指標都放到「各自的 '\0'」
再同步往回走:k==k ✓ r==r ✓ o==o ✓ w==w ✓
pt 回到 t 的開頭 -> t 的每個字元都對上了 -> 回傳 1
- 步驟拆解:
- 先算出兩個長度
slen、tlen(自己寫的指標版my_strlen,順便複習練習 2); - 提前排除:tlen > slen時不可能在尾端,直接回 0—— 這一步也保證了之後ps往回走不會越過s的開頭; - 讓ps = s + slen、pt = t + tlen(都指向'\0'); -while (pt > t):每輪先--再比較,任何一對字元不同就回 0; -pt走回開頭代表t全部比完且都相同,回 1。 - 邊界驗證:
-
t為空:tlen == 0,迴圈一次都不跑,回 1(「空字串是任何字串的尾端」,與標準庫慣例一致); -s == t(整串相等):也算尾端,回 1。
參考解答
/*
* ex5_strend.c
* ------------
* 練習 5:my_strend —— 判斷字串 t 是否出現在 s 的尾端(難度:進階)
* 出處:K&R 習題 5-4
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex5_strend.c
* 執行:/tmp/out
*/
#include <stdio.h>
/* 指標版 strlen:給下面的 my_strend 使用 */
static size_t my_strlen(const char *s)
{
const char *p = s;
while (*p != '\0')
p++;
return (size_t)(p - s);
}
/*
* 若 t 出現在 s 的尾端回傳 1,否則回傳 0。
* 想法:把兩個指標分別對齊到「各自的結尾」,再往回逐一比對。
* 例: s = "homework",t = "work"
* ps pt
* v v
* s: h o m e w o r k t: w o r k
* 從尾巴往前比 k==k, r==r, o==o, w==w -> t 比完了 -> 回傳 1
*/
int my_strend(const char *s, const char *t)
{
size_t slen = my_strlen(s);
size_t tlen = my_strlen(t);
if (slen < tlen) /* t 比 s 長,不可能在尾端 */
return 0;
const char *ps = s + slen; /* 指向 s 的 '\0' */
const char *pt = t + tlen; /* 指向 t 的 '\0' */
/* 從尾巴往回走 tlen 步,逐字比較 */
while (pt > t) {
ps--;
pt--;
if (*ps != *pt)
return 0;
}
return 1;
}
static void test(const char *s, const char *t)
{
printf("my_strend(\"%s\", \"%s\") = %d\n", s, t, my_strend(s, t));
}
int main(void)
{
test("homework", "work"); /* 1:正好在尾端 */
test("homework", "home"); /* 0:在開頭不算 */
test("homework", "homework"); /* 1:整串相等也算尾端 */
test("work", "homework"); /* 0:t 比 s 長 */
test("homework", ""); /* 1:空字串是任何字串的尾端 */
test("abc", "xbc"); /* 0:最後兩字元同、第一字元不同 */
return 0;
}
程式碼講解
if (slen < tlen) return 0;:這行不只是提前優化——它是安全前提。 少了它,ps往回走可能越過s的開頭(越界讀)。ps--; pt--;放在比較之前:兩個指標一開始指向'\0'(不需要比), 先退一步才是最後一個真正的字元。- 迴圈條件
pt > t:pt退回t的開頭時,t的所有字元都已比對成功。 用「指標比較」控制迴圈正是本單元的核心語法。 - 這題也可以「從前面比」:
return strcmp(s + slen - tlen, t) == 0;—— 概念相同(把 s 的尾段對齊 t),但那是下一單元的庫函式;這裡練的是純指標操作。
範例輸出
my_strend("homework", "work") = 1
my_strend("homework", "home") = 0
my_strend("homework", "homework") = 1
my_strend("work", "homework") = 0
my_strend("homework", "") = 1
my_strend("abc", "xbc") = 0
練習 6:my_strcat —— 只用指標實作字串串接(難度:挑戰)
題目
實作 void my_strcat(char *s, const char *t):把字串 t 接到字串 s 的尾端。
限制:只准用指標運算,不准用 [] 下標。
呼叫端保證 s 的空間足夠。用一個夠大的字元陣列連續串接 ", "、"world"、"!" 測試,
並測「接上空字串」與「從空字串開始接」兩個邊界情況。
解題思路
- 拆成兩個階段,各自都是前面練過的模式:
- 階段一(找接縫):
s目前的內容以'\0'結尾,新字元必須從那裡開始寫。 所以先讓s前進到'\0':while (*s != '\0') s++;——這就是 strlen 的走法,只是不用回傳長度。 - 階段二(複製):把t的每個字元(含結尾'\0')抄到s之後。 這正是 K&R 的 strcpy 慣用句:while ((*s++ = *t++) != '\0') ;
階段一結束時:
buf: [ H ][ e ][ l ][ l ][ o ][\0][ ][ ] ...
^
s(停在 '\0' 上,從這裡覆寫)
階段二逐字搬運 t = ", ":
buf: [ H ][ e ][ l ][ l ][ o ][ , ][ ][\0] ...
- 為什麼
(*s++ = *t++)能當迴圈條件? 賦值運算式的「值」就是被複製的那個字元。複製到'\0'時運算式的值是 0(假), 迴圈結束——而且'\0'已經被複製了,字串的新結尾自動就位。 - 參數可以直接拿來走:
s、t是位址的複本,函式內把它們一路++前進 不影響呼叫端;也因此不需要額外的區域指標。 - 呼叫端的責任:
s必須是「夠大的可修改陣列」。 拿字串字面值(char *s = "Hello")來接會當掉——字面值唯讀,這是上一單元的重點陷阱。
參考解答
/*
* ex6_my_strcat.c
* ---------------
* 練習 6:my_strcat —— 只用指標實作字串串接(難度:挑戰)
*
* 編譯:cc -std=c11 -Wall -Wextra -o /tmp/out ex6_my_strcat.c
* 執行:/tmp/out
*/
#include <stdio.h>
/*
* 把 t 接到 s 的尾端。呼叫端必須保證 s 的空間夠大。
* 全程只用指標,不用下標:
* 第 1 步:讓 s 前進到自己的 '\0'(找到接縫處)
* 第 2 步:用 K&R 的複製慣用句把 t 整串(含 '\0')抄過去
*/
void my_strcat(char *s, const char *t)
{
while (*s != '\0') /* 第 1 步:走到 s 的結尾 */
s++;
while ((*s++ = *t++) != '\0') /* 第 2 步:逐字複製,連 '\0' 一起搬 */
;
}
int main(void)
{
/* 關鍵:s 必須是「夠大的可修改陣列」,不能是字串字面值指標 */
char buf[32] = "Hello";
printf("串接前:\"%s\"\n", buf);
my_strcat(buf, ", ");
my_strcat(buf, "world");
my_strcat(buf, "!");
printf("串接後:\"%s\"\n", buf);
/* 邊界:接上空字串,內容應不變 */
my_strcat(buf, "");
printf("接空字串:\"%s\"\n", buf);
/* 邊界:從空字串開始接 */
char empty[16] = "";
my_strcat(empty, "abc");
printf("空字串接 \"abc\":\"%s\"\n", empty);
return 0;
}
程式碼講解
- 階段一不能用複製慣用句:那會把
t從s的開頭覆寫。先「走到底」再「開始抄」,順序是靈魂。 while ((*s++ = *t++) != '\0') ;:一行做了四件事——讀*t、寫*s、兩個指標各前進一步、 用複製的字元當迴圈條件。拆開來寫也完全可以(更易讀):
while (*t != '\0') {
*s = *t;
s++;
t++;
}
*s = '\0'; /* 拆開版要記得自己補結尾! */
拆開版最常見的 bug 就是忘記補 '\0';精簡版連 '\0' 一起複製,不會漏。
- char buf[32] = "Hello";:剩餘空間會被初始化規則清零,但程式不依賴這點——
串接永遠自己寫 '\0'。
- 接空字串:階段二第一輪就複製到 '\0',等於只是把結尾重寫一次,內容不變。
- 這個函式與標準庫 strcat 的差異:標準版會回傳 s(原始開頭)。
想照抄規格的話,進函式先存 char *head = s;,最後 return head;。
範例輸出
串接前:"Hello"
串接後:"Hello, world!"
接空字串:"Hello, world!"
空字串接 "abc":"abc"
常見錯誤解法對照
| 錯誤寫法 | 症狀 | 正確做法 |
|---|---|---|
(double)(sum / n) |
小數被整數除法截掉才轉型,平均值變整數 | (double)sum / n |
hi = a + n 當「最後一個元素」 |
從尾後開始交換:越界寫入 | hi = a + n - 1 |
my_strchr 迴圈寫 while (*s != c) |
c 不存在時衝出字串(越界讀) |
邊界條件用 '\0',比對放迴圈內 |
my_strend 沒先檢查 tlen > slen |
ps 往回走越過 s 開頭 |
長度不夠直接回 0 |
my_strcat 拆開寫忘了補 '\0' |
串接後的字串沒有結尾,printf 讀到垃圾 | 迴圈後 *s = '\0';(或用精簡版) |
對 char *s = "Hello" 做串接 |
寫入唯讀區,macOS 直接 crash | 目的地用可修改陣列 char buf[N] |