C语言字符串、字符与内存函数详解及模拟实现
C语言开发中经常需要操作字符数组和内存块,本文系统讲解strlen、strcpy、strcat、strcmp等核心字符串函数,以及strstr、strtok等查找分割工具;同时深度剖析字符分类与转换API。针对易错的缓冲区溢出问题,介绍长度受限的安全替代方案。最后通过指针运算逻辑,完整实现了memcpy、memmove、memcmp和memset等底层内存函数。
求字符串长度
第1步:理解 strlen 返回值与参数要求
strlen 函数用于计算字符串中包含的有效字符个数,不包含末尾的空字符 '\0'。该函数返回类型为无符号整数 size_t。
其原型为:size_t strlen( const char *string );
使用示例:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
int len = strlen(arr);
printf("%d\n", len);
return 0;
}
第2步:strlen 初阶模拟实现
利用循环遍历直到遇到空字符,统计计数器即可。需确保指针非空:
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);
int count = 0;
while (*str != '\0')
{
str++;
count++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
第3步:strlen 进阶模拟(指针相减)
利用指针算术特性,记录起始地址与结束地址的差值:
size_t my_strlen(const char* str)
{
assert(str != NULL);
char* head = str;
while (*str++); // 找到结尾处
return str - head - 1; //减去多走的一步和'
'的影响
}
关键注意事项
- 必须以 '\0' 作为结束标志,否则将越界访问导致随机值。
- 返回值为
size_t(无符号),在进行减法或比较时需注意算术转换陷阱。
长度不受限制的字符串函数
第4步:strcpy 拷贝逻辑与安全验证
strcpy(dest, src) 将源字符串(含 '\0')复制到目标空间。原型:char* strcpy(char * destination, const char * source );
char arr1[] = "abcdef";
char arr2[10] = "";
strcpy(arr2, arr1);
printf("%s\n", arr2);
模拟实现要点
必须保存目标起始地址并返回,确保源串以 '\0' 结尾且目标空间足够大:
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*src != '\0') {
*dest = *src;
dest++; src++;
}
*dest = *src; // 拷贝 '
'
return ret;
}
注意:如果目标空间小于源字符串,将引发严重的缓冲区溢出漏洞,这是面试常考点。
第5步:strcat 追加逻辑
strcat(dest, src) 在目标的末尾追加源的剩余部分。需要先在目标中找到 '\0' 的位置:
char arr1[] = "world!";
char arr2[20] = "hello ";
strcat(arr2, arr1);
printf("%s\n", arr2);
模拟实现
先定位目标尾部,再执行拷贝:
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest++) ; // 指向末尾'
'后一位
dest--; // 回退到'
'
while ((*dest++ = *src++));
return ret;
}
切勿让 strcat 对自己进行追加(如 strcat(a, a)),这会覆盖 '\0' 造成死循环。
第6步:strcmp 字节级比较规则
strcmp(s1, s2) 逐个比较字符 ASCII 码,返回差值。大于0表示前者大,等于0相等,小于0表示前者小:
char str1[] = "abcdef";
char str2[] = "abq";
int ret = strcmp(str1, str2);
printf("%d\n", ret);
模拟实现
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2 && *str1 != '\0' && *str2 != '\0') {
str1++; str2++;
}
if (*str1 != '\0' || *str2 != '\0') return *str1 - *str2;
return 0;
}
长度受限制的字符串函数(安全替代)
为避免前述函数的溢出风险,标准库提供了增加字节数参数的 strncpy、strncat 和 strncmp。它们并非绝对安全,但能有效限制操作范围。
第7步:strncpy 指定拷贝字节数
strncpy(dest, src, num) 只拷贝前 num 个字节。若源串短于 num,则用 '\0' 填充至 num:
char arr1[] = "abcdef";
char arr2[10] = "";
strncpy(arr2, arr1, sizeof(arr1));
模拟细节
当检测到 \0 时需要继续补全零以符合标准行为:
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
while (num--) {
*dest = *src;
if (*dest == '\0') break;
dest++; src++;
}
// 标准库会在拷贝完字符串后将剩余位置填0
while(num-- > 0) { *dest++ = 0; }
return ret;
}
第8步:strncat 安全追加
追加不超过 num 个字节的内容,并在末尾自动补一个 '\0':
char arr1[] = "world!";
char arr2[20] = "hello ";
strncat(arr2, arr1, 6);
第9步:strncmp 前 N 位比较
仅比较两个字符串的前 num 个字符:
char arr1[] = "abcdef";
char arr2[] = "abcq";
int len = strncmp(arr1, arr2, 3);
字符串查找与分隔函数
第10步:strstr 子串定位算法
strstr(str1, str2) 查找 str2 是否为 str1 的子串,成功返回起始指针,失败返回 NULL:
char arr1[] = "abbbcdef";
char arr2[] = "bcd";
char* ret = strstr(arr1, arr2);
printf("%s\n", ret);
朴素匹配模拟实现
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* p1 = str1;
const char* p2 = str2;
const char* mark = str1; // 记录尝试匹配的首位
while (*mark != '\0') {
p1 = mark;
p2 = str2;
while (*p1 == *p2 && *p1 != '\0' && *p2 != '\0') {
p1++; p2++;
}
if (*p2 == '\0') return (char*)mark;
mark++;
}
return NULL;
}
注:此方法时间复杂度为 O(N*M)。追求极致性能时可查阅 KMP 算法。
第11步:strtok 字符串分割处理
strtok(str, sep) 按照分隔符集合 sep 切割字符串。因为会修改原始字符串并保留内部状态,推荐使用以下循环模式:
char* sep = "@.";
char email[] = "test@qq.com";
char tmp[50];
strcpy(tmp, email); // 务必拷贝一份操作
for(char* token = strtok(tmp, sep); token != NULL; token = strtok(NULL, sep)) {
printf("Part: %s\n", token);
}
第12步:strerror 错误码翻译
配合全局变量 errno 查看具体错误原因:
#include <errno.h>
FILE* pf = fopen("non_exist.txt", "r");
if(pf == NULL) {
printf("Error: %s\n", strerror(errno));
}
字符函数与内存操作 API
第13步:字符分类与转换
C 语言头文件 <ctype.h> 提供了一系列对单个字符进行检查的函数,判断其是否为控制符、空白、数字、十六进制数等,也可执行大小写转换:
- 检查类:
iscntrl,isspace(空格/换页/回车等),isdigit(0-9),isxdigit(0-9, a-f, A-F),islower,isupper,isalpha,isalnum,ispunct,isgraph,isprint. - 转换类:
tolower(转小写),toupper(转大写).
第14步:memcpy 基础内存拷贝
处理不重叠内存块的高效拷贝。原型:void* memcpy(void* dest, const void* src, size_t num);
int arr1[] = {1,2,3,4};
int arr2[10] = {0};
memcpy(arr2, arr1, 4 * sizeof(int));
模拟实现与陷阱警告
按字节推进是最通用的做法:
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while(num--){
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
严格限制:根据 C 标准,内存发生重叠时调用 memcpy 会导致未定义行为。尽管 VS 等特定编译器可能做了兼容处理,生产环境请务必避开。
第15步:memmove 安全处理重叠内存
memmove 是专门为解决内存重叠而设计的。它的逻辑比 memcpy 复杂,需要根据首地址判定方向:
- 若
dest < src,从前往后拷贝(等价于 memcpy)。 - 若
dest > src,必须从后往前拷贝以防前面的旧数据被覆盖:
int arr[] = {1,2,3,4,5,6,7,8,9};
my_memmove(arr+1, arr+3, 4 * sizeof(int));
完整代码
void* my_memmove(void* dest, void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if(dest < src) {
// 从前向后
while(num--){
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
} else {
// 从后向前
while(num--){
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
第16步:memcmp 与 memset 设置
memcmp 比较两块内存前 num 字节的差异,返回正/负/零。适用于结构体比对:
int arr1[] = {1,2,3,4,5};
int arr2[] = {1,2,3};
int ret = memcmp(arr1, arr2, 3 * sizeof(int));
int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
assert(ptr1 && ptr2);
size_t count = 0;
while(count < num && *(char*)ptr1 == *(char*)ptr2) {
count++;
ptr1 = (char*)ptr1 + 1;
ptr2 = (char*)ptr2 + 1;
}
return count < num ? (*(char*)ptr1 - *(char*)ptr2) : 0;
}
memset 将一段连续内存的所有字节都设置为特定字符(常用于清零或赋值初始化):
char str[] = "hello world";
memset(str, 'x', 5);
printf("%s\n", str); // 输出 xxxxx world
void* my_memset(void* dest, int c, size_t num)
{
assert(dest);
void* ret = dest;
while(num--){
*(char*)dest = c;
dest = (char*)dest + 1;
}
return ret;
}
以上就是C语言字符串函数,字符函数,内存函数使用及模拟实现的详细内容,更多关于C语言字符串的资料请关注本站其它相关文章!免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









