標準庫字符串函數是 C 程序員每天打交道的基石。本文精選 strcat、strcmp、strcpy、strncpy、strrchr 五大核心函數,**給出經典實現源碼 (兼容主流 libc 風格)**,逐行解析指針算術、斷言防禦、返回值設計、邊界處理,幫助讀者從「會用」進階到「懂實現、能手寫、知優化」。
strcat:字符串拼接 (追加)
char *strcat(char *restrict dest, const char *restrict src);
// 將 src 拼接到 dest 末尾 (覆蓋 dest 的 '\0'),返回 dest
// 調用者保證 dest 空間足夠容納結果
char *strcat(char *dest, const char *src) {
char *ret = dest; // 保存原始 dest,用於返回 (鏈式調用)
// 1. 定位 dest 末尾 '\0'
while (*dest) dest++; // 等價 while (*dest != '\0') dest++;
// 注意:不能寫 while (*dest++),會多遞增一次導致指向 '\0' 後
// 2. 拷貝 src 直到 '\0' (含 '\0')
while ((*dest++ = *src++) != '\0')
; // 空循環體,賦值結果即為判斷條件
return ret;
}
strcat(strcat(buf, s1), s2) 鏈式調用。dest 足夠大,否則緩衝區溢出。**生產代碼首選 strncat 或 strlcat**。*dest 再 dest++,確保停在 \0 上;拷貝時 *dest++ = *src++ 將賦值與判斷融合。strcmp:字符串比較 (字典序)
int strcmp(const char *s1, const char *s2);
// 返回 <0, =0, >0 對應 s1 <, =, > s2 (按 unsigned char 比較)
int strcmp(const char *s1, const char *s2) {
// 轉為 unsigned char 避免負值字符比較異常 (符號擴展)
const unsigned char *p1 = (const unsigned char *)s1;
const unsigned char *p2 = (const unsigned char *)s2;
while (*p1 && *p1 == *p2) { // 相同且未結束
p1++;
p2++;
}
// 差異處或結束處:返回差值 (自動轉為 int)
return *p1 - *p2;
}
char 有符號時 0xFF > 0x01 判斷錯誤。'a' - 'b' = -1),但符號正確即可。strcpy:字符串拷貝
char *strcpy(char *restrict dest, const char *restrict src);
// 拷貝 src (含 '\0') 到 dest,返回 dest
char *strcpy(char *dest, const char *src) {
char *ret = dest;
while ((*dest++ = *src++) != '\0')
;
return ret;
}
char *strcpy(char *dest, const char *src) {
assert(dest != NULL && src != NULL); // 防禦性編程
char *ret = dest;
while ((*dest++ = *src++) != '\0')
;
return ret;
}
!= '\0' 確保結束符也被拷貝。restrict 關鍵字 (C99) 告訴編譯器 dest 與 src 不重疊,允許向量化優化。重疊拷貝需用 memmove。strncpy:定長字符串拷貝 (固定寬度字段處理)
char *strncpy(char *dest, const char *src, size_t n);
// 最多拷貝 n 字符。src 短於 n → dest 補 '\0' 至 n。src 長於 n → 截斷,**不自動添加 '\0'**。
char *strncpy(char *dest, const char *src, size_t n) {
char *ret = dest;
while (n--) {
if ((*dest = *src) != '\0') src++; // src 未結束才推進 src
dest++;
}
return ret;
}
strlen(src) >= n,dest 無結束符,後續 strlen/printf 會越界讀取。dest[n-1] = '\0'; (若 n>0)。strlcpy (BSD) / strncpy_s (C11 Annex K)**:保證以 '\0' 結尾,返回源長度。strrchr:反向查找字符最後出現位置
char *strrchr(const char *s, int c);
// 查找 c (轉為 unsigned char) 在 s 中**最後一次**出現的地址,含結束符。
// 找到返回指針,否則 NULL。
strchr 循環)
char *strrchr(const char *s, int c) {
c = (unsigned char)c;
if (c == '\0') return strchr(s, '\0'); // 查結束符直接用 strchr
const char *found = NULL;
const char *p;
while ((p = strchr(s, c)) != NULL) { // 反覆向前查找
found = p;
s = p + 1; // 從下一字符繼續找
}
return (char *)found;
}
strchr 復用:strchr 通常高度優化 (SIMD/汇編),反覆調用比手寫反向循環更快。strchr,避免循環邏輯錯誤。const char* → char* (標準規定),調用者需知曉原字符串可修改性。while (*p++) 比 for(i=0; s[i]; i++) 更快、更符合 C 語言本質。while ((*d++ = *s++) != '\0') 融合拷貝與終止檢查,零分支。unsigned char,符合標準、避免符號擴展 Bug。assert(ptr != NULL) 捕獲空指針傳入,零發布版開銷 (NDEBUG)。strncat/strlcpy/snprintf 系列**。strcpy 模板
char *my_strcpy(char *dest, const char *src) {
if (!dest || !src) return NULL; // 或 assert
char *ret = dest;
// 可選:檢查重疊 (dest 在 src 區間內) → 用 memmove 邏輯
while ((*dest++ = *src++) != '\0');
return ret;
}