`strcat` 看似簡單,實則蘊含 C 指針操作、內存佈局、函數契約設計的核心智慧。本文通過分析 3 種手寫實現,揭示常見陷阱,對比標準庫設計哲學。
char* strcat1(char* to, char* from) {
char* save = to;
for (; *save; save++); // 1. 找到 to 末尾 \0
while ((*save++ = *from++) != '\0'); // 2. 複製 from 含 \0
return to; // 關鍵:返回目標起始地址
}
char* strcat2(char* to, char* from) {
char* save = to;
for (; *save; save++); // 找尾
for (; (*save = *from) != '\0'; save++, from++); // 複製
return to;
}
char* strcat3(char* to, char* from) {
char* save = to;
while (*save) save++; // 找尾
while ((*save++ = *from++) != '\0'); // 複製
return to;
}
char c[20] = {0}; // 正確:全零初始化,c[0]=='\0'
char c[20]; // 錯誤:棧上垃圾值,c[0] 可能非 '\0'
strcat3(c, "Hello"); // 若 c[0]!='\0',找尾循環越界訪問,導致 UB、崩潰或靜默錯誤
教訓:**C 中局部數組/結構體必須顯式初始化**(`= {0}` 或 `memset`),否則行為未定義。
不能!
結果:複製起始位置偏移一字節,導致字符串內容錯誤、長度計算錯誤、甚至緩衝區溢出。
// 錯誤返回
return save; // 指向結果字符串末尾的 \0 之後
// 標準返回
return to; // 指向目標字符串起始地址
設計意圖:支持鏈式調用
// 鏈式構建字符串
strcpy(buf, strcat(strcat(strcat(buf, "prefix_"), name), "_suffix"));
// 等價於
strcpy(buf, "prefix_");
strcat(buf, name);
strcat(buf, "_suffix");
若返回末尾指針,鏈式調用將失敗。這是 C 標準庫函數**統一返回目標起始地址**的慣例(`strcpy`、`strcat`、`sprintf` 等)。
char *p; // K&R 風格:強調 * 綁定變量 p,p 是指針
char* p; // C++ 風格:強調類型是 char*,但易誤導:char* p, q; → q 是 char 非指針
char * p; // 折中風格
最佳實踐:
char *strcat(char *dest, const char *src) {
char *save = dest;
for (; *dest; ++dest); // 找尾
while ((*dest++ = *src++) != '\0'); // 複製含 \0
return save; // 返回起始地址
}
關鍵差異:
// 標準 C:strncat — 但不保證結果以 \0 結尾(若 src 過長)
char *strncat(char *dest, const char *src, size_t n);
// BSD 擴展:strlcat — 保證 \0 結尾,返回嘗試創建的總長度
size_t strlcat(char *dest, const char *src, size_t size);
// 現代 C++:直接用 std::string
std::string s = "Hello";
s += " World"; // 安全、無長度限制、自動內存管理
核心洞見: `strcat` 這類「簡單」函數,實則是 C 語言**指針算術、內存契約、API 設計哲學**的縮影。手寫實現不僅是練習指針,更是理解標準庫「為什麼這樣設計」的最佳切入點。工程上,**能用封裝類型(`std::string`)絕不用裸指針操作字符串**。