本檔收錄本單元的練習題、完整解題思路與參考解答。 強烈建議先自己動手做:看完題目就闔上這份檔案,寫出你的版本、編譯、執行、卡住了再回來看「解題思路」; 真的做完了,再對照「參考解答」與「程式碼講解」。每題的完整程式碼也放在 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 } 測試,另外測「單一元素」的邊界情況。

解題思路

  1. 回想三種等價的走訪寫法(單元講義第 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 安全。

  1. 累加型別的選擇:int 加總大陣列可能溢位,用 long 累加穩妥。
  2. 平均值的型別陷阱:sum / n 是整數除法,小數會被截掉! 必須先把其中一邊轉成 double:(double)sum / n。
  3. 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'),分別寫出兩個版本:

  1. 下標版 my_strlen_idx:用索引 i 數到 '\0';
  2. 指標相減版 my_strlen_ptr:讓指標 p 走到 '\0',回傳 p - s。

用 "hello"、空字串 ""、單字元 "a"、"now is the time" 驗證兩版結果一致。

解題思路

  1. 字串的本質:以 '\0' 結尾的字元陣列。「長度」就是 '\0' 之前的字元個數。
  2. 下標版很直觀:從 i = 0 開始,s[i] 不是 '\0' 就 i++; 迴圈結束時 i 恰好是長度(s[i] 正停在 '\0' 上)。
  3. 指標相減版是 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 = 字串長度
  1. 回傳型別:長度不會是負的,標準庫用 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'。

解題思路

  1. 回傳值設計:這題的重點是「回傳指標而不是索引」。 回傳指標的好處:呼叫端拿到後可以直接當「子字串」用(從該處印到結尾), 也可以用 p - s 換算回索引——資訊沒有損失。
  2. 主迴圈:讓 s 逐字前進,每一步比較 *s == c;找到就回傳目前的 s。 走到 '\0' 都沒找到,回傳 NULL。
  3. 特例——搜尋 '\0':標準規定 strchr(s, '\0') 要回傳指向結尾 '\0' 的指標。 主迴圈在 *s == '\0' 時就停了,比不到結尾那格,所以迴圈後補一個判斷。
  4. 為什麼參數是 int c 卻要轉 (char)c 比較? 標準庫介面就是 int(歷史因素),實作時轉回 char 再比,語意才正確。
  5. (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 個和最後 1 個交換、第 2 個和倒數第 2 個交換……」。
  2. 雙指標設計: - lo = a:指向最前面; - hi = a + n - 1:指向最後一個元素(注意不是尾後 a + n,那格不能碰!); - 每一輪:交換 *lo 與 *hi,然後 lo++、hi--,兩端向中間夾攻。
  3. 什麼時候停? 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 完全相等。

解題思路

  1. 換個方向想:「出現在尾端」代表兩個字串的結尾對齊後完全重合。 與其從前面對齊再算偏移,不如從後面往前比:
   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
  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。
  2. 邊界驗證: - 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"、"!" 測試, 並測「接上空字串」與「從空字串開始接」兩個邊界情況。

解題思路

  1. 拆成兩個階段,各自都是前面練過的模式: - 階段一(找接縫):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] ...
  1. 為什麼 (*s++ = *t++) 能當迴圈條件? 賦值運算式的「值」就是被複製的那個字元。複製到 '\0' 時運算式的值是 0(假), 迴圈結束——而且 '\0' 已經被複製了,字串的新結尾自動就位。
  2. 參數可以直接拿來走:s、t 是位址的複本,函式內把它們一路 ++ 前進 不影響呼叫端;也因此不需要額外的區域指標。
  3. 呼叫端的責任: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]