字符函數和字符串函數
- 字符函數和字符串函數
- 1. strlen
- strlen 函數詳解
- 模擬實現
- 1.計數器方式
- 2.不能創建臨時變量計數器(遞歸)
- 3.指針-指針的方式
- 2. strcpy
- strcpy 函數詳解
- 模擬實現
- 3. strcat
- strcat 函數詳解
- 模擬實現
- 4. strcmp
- strcmp 函數詳解
- 模擬實現
- 5. strncpy
- strncpy 函數詳解
- 模擬實現
- 6.strncat
- strncat 函數詳解
- 模擬實現
- 7.strncmp
- strncmp 函數詳解
- 模擬實現
- 8. strstr
- strstr 函數詳解
- 模擬實現
- 9. strtok
- strtok 函數詳解
- 模擬實現
- 10.strerror
- strerror 函數詳解
- 模擬實現
- 11.字符分類函數
- 12.memcpy
- memcpy 函數詳解
- 模擬實現
- 13.memmove
- memmove 功能詳解
- 14.memcmp
- memcmp 功能詳解
- 15. memset
- memset 功能詳解
- 模擬實現
字符函數和字符串函數
1. strlen
strlen 函數詳解
功能
計算字符串長度,返回 \0
前的字符個數(不包含 \0
)。
關鍵特性
-
以
\0
為結束標志
字符串必須包含\0
,否則會越界訪問內存。char arr[] = {'a', 'b', 'c'}; // 無 `\0` printf("%zu\n", strlen(arr)); // 輸出隨機值
-
參數合法性
指針需指向以\0
結尾的有效字符串,傳入NULL
會觸發段錯誤。char* ptr = NULL; strlen(ptr); // 段錯誤
-
返回值為
size_t
無符號類型,與負數比較可能導致邏輯錯誤。if (strlen("abc") - 5 < 0) { // 永遠為假printf("Less than 0\n"); // 不會執行 }
模擬實現
1.計數器方式
size_t strlen(const char* str)
{assert(str);int num = 0;while (*str != '\0'){num++;str++;}return num;
}
2.不能創建臨時變量計數器(遞歸)
size_t strlen(const char * str)
{if(*str == '\0')return 0;elsereturn 1+my_strlen(str+1);
}
3.指針-指針的方式
size_t strlen(char *s)
{char *p = s;while(*p != ‘\0’ )p++;return p-s;
}
2. strcpy
strcpy 函數詳解
功能
復制源字符串到目標空間,包含 \0
。
關鍵特性
-
源串需包含
\0
若源串無\0,strcpy會持續復制直至遇到內存中的\0,導致緩沖區溢出。char src[] = {'a', 'b', 'c'}; // 無 `\0` char dest[10]; strcpy(dest, src); // 越界復制
-
目標空間需足夠大
目標數組長度需至少為源串長度 + 1(含\0)。若空間不足,會覆蓋相鄰內存。char dest[3]; strcpy(dest, "abcdef"); // 緩沖區溢出
-
目標空間必須可寫
目標指針不能指向常量字符串或只讀內存。
錯誤示例:char* dest = "hello"; // 只讀內存 strcpy(dest, "world"); // 段錯誤
模擬實現
char* strcpy(char* destination, const char* source)
{assert(destination&&source);char* tmp = destination;while (*tmp++ = *source++){;}*tmp = '\0';return destination;
}
3. strcat
strcat 函數詳解
功能
將源字符串追加到目標字符串末尾。
關鍵特性
-
源串和目標串需包含
\0
源串必須以\0結尾,否則會導致越界復制。
示例:char src[] = {'a', 'b', 'c'}; // 無 `\0` char dest[10] = "hello"; strcat(dest, src); // 越界復制
-
目標空間需足夠大
目標數組需容納原內容 + 源串 +\0
。
計算示例:char dest[10] = "abc"; strcat(dest, "defgh"); // 需 4 + 5 + 1 = 10 字節
模擬實現
char* strcat(char* destination, const char* source)
{assert(destination&&source);char* tmp= destination;while (*tmp!='\0'){tmp++;}strcpy(tmp, source);return destination;
}
4. strcmp
strcmp 函數詳解
功能
逐字符
比較兩字符串,返回比較結果。
返回值規則
> 0
:首個不匹配字符中,s1
的 ASCII 值大于s2
。= 0
:兩字符串完全相同。 (長度和內容均一致)< 0
:首個不匹配字符中,s1
的 ASCII 值小于s2
。
比較邏輯:
- 按字節比較:從首字符開始逐字節比較,直到字符不等或遇\0。
- 大小寫敏感:‘A’(65) < ‘a’(97)。
- 長度影響結果:若短串是長串的前綴,則短串 < 長串。
示例
strcmp("abc", "abd"); // 返回負值('c' < 'd')
strcmp("abc", "abc"); // 返回0
strcmp("abc", "ab"); // 返回正值('\0' < 'c')
strcmp("123", "12"); // 返回正值('3' > '\0')
strcmp("A", "a"); // 返回負值(65 < 97)
模擬實現
int strcmp(const char * str1, const char * str2)
{assert(str1&&str2);while (*str1 == *str2){if (*str1 == '\0')return 0;str1++;str2++;}return (*str1 - *str2);
}
5. strncpy
strncpy 函數詳解
功能
復制源串的前 n
個字符到目標空間。
特殊規則
- 源串長度 <
n
:補\0
至n
字節。
實例:char dest[5]; strncpy(dest, "abc", 4); // "abc\0\0"
- 源串長度 ≥
n
:不自動補\0
,需手動處理。
易錯示例:
char dest[3];
strncpy(dest, "abcdef", 3); // dest內容:"abc"(無`\0`)
printf("%s\n", dest); // 可能輸出亂碼(繼續讀取后續內存)
模擬實現
char * strncpy(char * destination, const char * source, size_t num)
{assert(destination&&source);char* tmp = destination;while (*source&&num>0){*tmp++ = *source++;num--;}while (num>0){*tmp++ = '\0';num--;}return destination;
}
6.strncat
strncat 函數詳解
功能
追加源串的前 n
個字符到目標串末尾。
特殊規則
-
追加后自動補\0:
無論n
是否大于源串長度,strncat都會在追加后添加\0。`char dest[10] = "abc"; strncat(dest, "def", 5); // "abcdef\0"
-
源串長度<
n
: -
僅追加至
\0
,不補多余字符。
示例:char dest[10] = "abc"; strncat(dest, "de", 5); // 追加"de",結果:"abcde\0"
-
目標空間需足夠大:
需容納原內容 + 源串前n字節(或全部) +\0
。
模擬實現
char * strncat(char * destination, const char * source, size_t num)
{assert(destination&&source);char* tmp = destination;while (*tmp != '\0'){tmp++;}while (*source&&num>0){*tmp++ = *source++;num--;}*tmp = '\0';return destination;
}
7.strncmp
strncmp 函數詳解
功能
比較兩字符串的前 n
個字符。
返回值規則:
同strcmp
,但僅比較至n
字節或\0
。
示例
strncmp("abcdef", "abcxyz", 3); // 返回0(前3字節相同)
strncmp("abc", "abcd", 4); // 返回負值(`\0` < 'd')
strncmp("123", "12a", 2); // 返回0(僅比較前2字節)
與strcmp
的區別:
strncmp
最多比較n
字節,而strcmp
會比較至\0
。
示例對比:
strcmp("abc", "abcdef"); // 返回0(`abc`與`abc`相等)
strncmp("abc", "abcdef", 6); // 返回負值(`abc\0\0\0` < `abcdef`)
模擬實現
int strncmp(const char * str1, const char * str2, size_t num)
{assert(str1&&str2);while ((*str1 == *str2)&&num>0){if (*str1 == '\0')return 0;str1++;str2++;num--;}if (num == 0)return 0;return (*str1 - *str2);
}
8. strstr
strstr 函數詳解
功能
查找子串在主串中的首次出現位置。
返回值
- 找到:返回子串首字符地址。
- 未找到:返回
NULL
。
匹配規則:
- 完全匹配:子串必須連續且完整出現。
- 空串處理:
strstr(s, "")
始終返回s
(空串是任何串的子串)。
示例
char* p = strstr("abcdef", "cde"); // p指向"cdef"
char* q = strstr("abc", "acd"); // q為NULL(未找到)
模擬實現
char* strstr(const char *str1, const char * str2)
{assert(str1&&str2);if (*str2 == '\0') {return (char*)str1; }const char* ps1 = str1;while (*ps1 != '\0'){const char* begin = ps1;const char* ps2 = str2;while (*ps1 != '\0' && *ps2 != '\0' && *ps1 == *ps2){ps1++;ps2++;}if (*ps2 == '\0')return (char*)begin;ps1 = begin+1;}if (*ps1 == '\0')return NULL;
}
9. strtok
strtok 函數詳解
功能
按分隔符分割字符串。
特性
- 破壞性操作:替換分隔符為
\0
。 并保存后續位置。
char str[] = "hello,world";
char* token = strtok(str, ","); // str變為"hello\0world",返回"hello"
-
狀態保存:
首次調用需傳入原串,后續傳NULL繼續分割。
示例:char str[] = "a,b,c"; char* t1 = strtok(str, ","); // 返回"a" char* t2 = strtok(NULL, ","); // 返回"b" char* t3 = strtok(NULL, ","); // 返回"c" char* t4 = strtok(NULL, ","); // 返回NULL(已無分隔符)
- 線程不安全:使用靜態變量保存狀態。
內部使用靜態變量保存位置,多線程環境下會導致沖突。
模擬實現
模擬實現原理:
- 首次調用時,找到首個非分隔符字符,將其后的分隔符替換為\0并記錄位置;
- 后續調用時,從記錄位置繼續查找,重復上述操作;
- 無剩余分隔符時返回NULL。
char * strtok(char* str, const char * sep) {static char* last = NULL; // 靜態變量保存上次處理的位置// 首次調用或顯式傳入NULL繼續處理if (str != NULL) {last = str;} else if (last == NULL) {return NULL; // 沒有更多標記}// 跳過前導分隔符while (*last != '\0' && strchr(sep, *last) != NULL) {last++;}if (*last == '\0') {last = NULL; // 沒有更多標記return NULL;}// 找到當前標記的結束位置char* token = last;while (*last != '\0' && strchr(sep, *last) == NULL) {last++;}if (*last != '\0') {*last = '\0'; // 替換分隔符為字符串結束符last++; // 移動到下一個位置} else {last = NULL; // 處理完畢,下次調用返回NULL}return token;
}
10.strerror
strerror 函數詳解
功能
將錯誤碼(errno)
轉換為錯誤信息字符串。
關鍵細節:
- 錯誤碼存儲在
errno
:
系統調用或庫函數出錯時會設置errno
,需包含<errno.h>
。
示例
FILE* fp = fopen("nonexistent.txt", "r");
if (!fp) {printf("Error: %s\n", strerror(errno)); // 輸出:"No such file or directory"
}
- 返回靜態字符串:
每次調用返回同一緩沖區,結果可能被后續調用覆蓋。
示例
char* err1 = strerror(1); // "Operation not permitted"
char* err2 = strerror(2); // "No such file or directory"
printf("%s\n", err1); // 可能輸出"No such file or directory"(被覆蓋)
模擬實現
char * strerror(int errnum) {// 錯誤信息數組(實際實現可能更大)static const char* const error_messages[] = {[0] = "No error",[1] = "Operation not permitted",[2] = "No such file or directory",[3] = "No such process",[4] = "Interrupted system call",[5] = "Input/output error",[6] = "Device not configured",[7] = "Argument list too long",[8] = "Exec format error",[9] = "Bad file descriptor",[10] = "No child processes",// 更多錯誤碼...};// 靜態緩沖區用于返回錯誤信息static char unknown_error[32];// 檢查錯誤碼是否在預定義范圍內size_t max_errors = sizeof(error_messages) / sizeof(error_messages[0]);if (errnum >= 0 && (size_t)errnum < max_errors && error_messages[errnum] != NULL) {return (char*)error_messages[errnum];}// 處理未知錯誤碼snprintf(unknown_error, sizeof(unknown_error), "Unknown error %d", errnum);return unknown_error;
}
11.字符分類函數
函數 | 判斷條件(返回真的情況) |
---|---|
iscntrl | 控制字符(如\t, \n, ASCII 0-31, 127) |
isspace | 空白字符(, \t, \n, \r, \f, \v) |
isdigit | 十進制數字(0-9) |
isxdigit | 十六進制數字(0-9, a-f, A-F) |
islower | 小寫字母(a-z) |
isupper | 大寫字母(A-Z) |
isalpha | 字母(a-z, A-Z) |
isalnum | 字母或數字(a-z, A-Z, 0-9) |
ispunct | 標點符號(非字母、數字、空白的可打印字符) |
isgraph | 圖形字符(非空白的可打印字符) |
isprint | 可打印字符(包括空白) |
12.memcpy
memcpy 函數詳解
功能
內存復制 (按字節),不處理重疊區域。
原型:
void* memcpy(void* dest, const void* src, size_t n);
關鍵特性:
- 按字節復制:
不關心內容是否為字符串,遇\0
不停止。
示例:
int src[5] = {1, 2, 3, 4, 5};
int dest[5];
memcpy(dest, src, 20); // 復制5個int(20字節)
- 不處理重疊區域:
若源/
目標區域重疊,結果未定義(可能數據覆蓋)。
錯誤示例:
int arr[10] = {1, 2, 3, 4, 5};
memcpy(arr + 2, arr, 12); // 重疊區域,結果不可預測
模擬實現
void * memcpy(void* destination, const void* source, size_t num)
{assert(destination != NULL || num == 0);assert(source != NULL || num == 0);if (destination == source) {return destination;}void *orig_dest = destination;while (num--) {*(char *)destination = *(char *)source;destination = (char *)destination + 1;source = (char *)source + 1;}return orig_dest;
}
13.memmove
memmove 功能詳解
功能:
- 處理重疊內存區域的復制。
特性詳解:
- 安全處理重疊內存區域
memmove
能夠安全處理源內存(src
)和目標內存(dest
)重疊的情況。通過檢查目標地址是否位于源地址的后半段,自動選擇從后向前或從前向后復制,避免數據覆蓋。
示例代碼
int arr[10] = {1, 2, 3, 4, 5};
memmove(arr + 2, arr, 20); // 復制前5個int(20字節)到位置2
// 結果:arr = {1, 2, 1, 2, 3, 4, 5, 0, 0, 0}
模擬實現
void * memmove(void * destination, const void * source, size_t num)
{assert(destination != NULL || num == 0);assert(source != NULL || num == 0);void *orig_dest = destination;if (destination < source) { memcpy(destination, source, num);}else{// ��?�������char *dst = (char *)destination + num - 1;const char *src = (const char *)source + num - 1;while (num--) {*(char *)dst = *(char *)src;dst = (char *)dst - 1;src = (char *)src - 1;}}return orig_dest;
}
14.memcmp
memcmp 功能詳解
**功能:**按字節比較內存區域。
按字節比較內存區域
memcmp
逐字節比較兩塊內存區域,返回值的符號由首個不匹配字節的差值決定:
- >0:
ptr1
的字節值大于ptr2
。 - =0:所有字節相等。
- <0:
ptr1
的字節值小于ptr2
。
示例代碼
char s1[] = {1, 2, 3};
char s2[] = {1, 2, 4};
memcmp(s1, s2, 2); // 返回0(前2字節相同)
memcmp(s1, s2, 3); // 返回負值(3 < 4)
與 strcmp 的區別
memcmp
嚴格比較指定字節數,不關心\0
。strcmp
遇到\0
停止比較。
對比示例
char a[] = "abc\0def";
char b[] = "abc\0xyz";
memcmp(a, b, 4); // 返回0(前4字節均為 `abc\0`)
strcmp(a, b); // 返回0(因 `\0` 終止比較)
模擬實現邏輯
int memcmp(const void * ptr1, const void * ptr2, size_t num)
{assert(ptr1 && ptr2);const char *p1 = (const char *)ptr1;const char *p2 = (const char *)ptr2;while (num-- > 0) {if (*p1 != *p2) return (char)*p1 - (char)*p2;p1++;p2++;}return 0;
}
15. memset
memset 功能詳解
功能:將內存塊的前n個字節設置為指定的字符值(按字節填充)。
函數原型
void* memset(void* ptr, int value, size_t n);
ptr
:指向要填充的內存塊的起始地址(可修改);value
:要設置的字符值(雖然類型是int,但實際只使用低 8 位,即unsigned char范圍);n
:要設置的字節數;返回值
:指向ptr的指針(方便鏈式操作)。
關鍵特性
- 按字節操作:
無論內存塊存儲的是何種類型(int、float等),memset
都會逐個字節設置為value
的低 8 位。
示例(正確用法):
char str[10];
memset(str, 'a', 5); // 前5字節設為'a',結果:"aaaaa????"(?為未初始化值)
- 對非字符類型的影響:
若用于int、long
等多字節類型,可能導致非預期結果(因每個字節都被設置為相同值)。
易錯示例:
int arr[5];
memset(arr, 1, 20); // 錯誤:每個字節設為1,每個int為0x01010101(十進制16843009)
// 期望:arr全為1 → 實際:arr元素為16843009
- n不可超過內存塊大小:
若n
大于ptr
指向的內存塊長度,會導致緩沖區溢出(覆蓋相鄰內存)。
典型用法
- 初始化字符數組(清空或填充特定字符):
char buf[100];
memset(buf, 0, 100); // 清空緩沖區(所有字節設為0)
- 初始化結構體(快速清零):
typedef struct {int a;char b[20];
} Stu;
Stu s;
memset(&s, 0, sizeof(Stu)); // 結構體所有成員清零
模擬實現
void * memset(void * ptr, int value, size_t num)
{assert(ptr);void *orig_ptr = ptr;while (num--){*(char*)ptr = (char)value;ptr = (char*)ptr + 1;}return orig_ptr;
}