字符函数和字符串函数
- 字符函数和字符串函数
- 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;
}