深入理解C语言字符串与内存操作函数
C语言标准库提供了强大的字符串与内存操作能力。本文梳理 `string.h` 中的核心函数,详细解析其功能、参数约束及安全边界,并附带底层原理模拟代码,助您高效处理文本与内存数据。
求字符串长度
第1步:获取字符串实际长度(strlen)
目的:统计以 '\0' 结束的字符串中有效字符的数量。
入口与动作:size_t strlen ( const char * str );。该函数从参数地址开始逐个向后读取字节,遇到 '\0' 停止,返回之前的字符计数。
关键约束:
- 源字符串必须包含终止符
'\0',否则会导致越界访问,返回值不可控。 - 返回类型为
size_t(无符号整型),表示长度恒为正或零。
底层模拟实现逻辑:
// 方式一:计数器法
size_t my_strlen(const char* str)
{
int count = 0;
assert(str != NULL);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
// 方式二:指针相减法(推荐)
size_t my_strlen(const char* str)
{
assert(str != NULL);
char* begin = str;
while (*str != '\0')
{
str++;
}
return str - begin; // 利用两个地址之差得到元素个数
}
长度不受限制的字符串操作
第2步:字符串拷贝(strcpy)
目的:将源字符串完整复制到目标内存区域。
原型:char* strcpy(char * destination, const char * source);
操作规范:
- 自动追加尾部:不仅复制内容,还会连同源串的
'\0'一起写入目标空间。 - 容量要求:
destination指向的空间必须足够大,且为可修改状态。 - 返回值:返回目标空间的起始地址,便于链式调用。
安全风险提示:若目标空间不足,将引发缓冲区溢出(Buffer Overflow)。建议在生产环境中优先使用长度受限的 strncpy。
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src); // 参数非空检查
while (*dest++ = *src++) // 循环直到赋值了 '\0' 后退出
{
;
}
return ret;
}
第3步:字符串拼接(strcat)
目的:将一个字符串追加到另一个字符串的末尾。
原型:char * strcat ( char * destination, const char * source );
执行逻辑:
- 首先定位
destination中的'\0'作为新的写入起点。 - 将
source的内容(含'\0')复制到该位置后。
注意:严禁 destination 和 source 为同一指针,这会导致覆盖源数据末尾的 '\0',从而引发死循环或越界。
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
// 寻找目标串末尾的空字符
while (*dest)
{
dest++;
}
// 从末尾开始拷贝
while (*dest++ = *src++)
{
;
}
return ret;
}
第4步:字符串比较(strcmp)
目的:按字典序比较两个字符串的大小。
原型:int strcmp ( const char * str1, const char * str2 );
规则:
- 逐字符比较 ASCII 码值。
- 返回负数:若第一个不同字符 str1 < str2。
- 返回正数:若第一个不同字符 str1 > str2。
- 返回 0:两字符串完全相同(包括结尾的
'\0')。
长度受限制的字符串操作
第5步:安全版字符串拷贝(strncpy)
目的:限制最大拷贝字节数,防止目标空间被撑破。
原型:char * strncpy ( char * destination, const char * source, size_t num );
行为细节:
- 最多拷贝前
num个字节。 - 若源串短于
num,剩余空间将由'\0'填充至num个。 - 警告:若源串长度大于等于
num,生成的新串将不会自动添加'\0'结尾,导致后续字符串函数出错。务必确保手动补位或在num-1的位置预留结束符。
第6步:安全版字符串拼接(strncat)
原型:char * strncat ( char * destination, const char * source, size_t num );
逻辑:从目标串末尾的空字符处起,最多追加 num 个字符,并在最后强制追加一个 '\0'。即使追加的数据截断了,最终结果依然是合法的 C 字符串。
第7步:指定长度比较(strncmp)
原型:int strncmp ( const char * str1, const char * str2, size_t num );
逻辑:仅比较前 num 个字符。当遍历完 num 个字符仍未发现差异,或者中途遇到了 '\0',则提前终止比较。
字符串查找与分割
第8步:子串查找(strstr)
原型:char * strstr ( const char *str1, const char * str2);
返回值:若找到子串,返回其在 str1 中首次出现的起始地址;未找到则返回 NULL。
char* my_strstr(const char* str1, const char* str2)
{
const char* s1 = str1;
const char* s2 = str2;
const char* cur = str1;
// 简化版暴力匹配逻辑
while (*cur)
{
s1 = cur;
s2 = str2;
while(*s1 && *s2 && *s1 == *s2)
{
s1++; s2++;
}
if (*s2 == '\0') return (char*)cur;
cur++;
}
return NULL;
}
第9步:基于分隔符切分(strtok)
原型:char * strtok ( char * str, const char * sep );
功能:将字符串按 sep 指定的字符集合进行分割。它会**修改原字符串**(插入 '\0' 切断标记)。
使用要点:
- 备份必要:原字符串会被破坏,若需保留,请先复制到临时缓冲区内再传入。
- 第一次调用:传原字符串指针。
- 后续调用:第一个参数传
NULL,函数将继续在上次切分后的位置继续查找。
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abc@def.123@456";
char buf[30] = { 0 };
strcpy(buf, arr); // 必须备份!
const char* sep = "@.";
char* str = NULL;
for (str = strtok(buf, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
return 0;
}
错误信息报告机制
第10步:获取系统错误描述(strerror)
场景:当系统调用失败时(如 malloc 内存不足),全局变量 errno 会被记录相应的错误码。调用 strerror(errno) 可将数字代码转换为人类可读的错误字符串。
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
int main() {
int* p = (int*)malloc(INT_MAX); // 申请超大内存可能失败
if (p == NULL) {
printf("错误: %s\n", strerror(errno));
}
free(p);
return 0;
}
字符分类与转换
以下函数定义在 ctype.h 中:
第11步:字符属性检测
| 函数 | 判定条件(真值) |
|---|---|
isalpha | 任意字母大小写 |
isdigit | 十进制数字 |
isspace | 空白符(空格、制表符、换行等) |
isalnum | 字母或数字 |
第12步:大小写转换
tolower(int c):转小写。toupper(int c):转大写。
内存块操作函数
这类函数定义在 string.h 中,它们不关心数据类型,直接对内存字节进行操作。
第13步:内存拷贝(memcpy)
原型:void * memcpy ( void * destination, const void * source, size_t num );
特点:
- 按
byte为单位进行连续复制。 - 不会在遇到
'\0'时停止。 - 严重约束:源区与目标区绝对不能重叠,否则结果未定义。
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
char* d = (char*)dest;
const char* s = (const char*)src;
while(count--)
{
*d++ = *s++;
}
return ret;
}
第14步:重叠内存移动(memmove)
原型:void * memmove ( void * destination, const void * source, size_t num );
必要性:如果内存区间发生重叠(例如将数组右移一位),memcpy 会因先写入后面的覆盖掉前面的源数据而报错。memmove 专门处理这种情况。
智能策略:
- 若
dest <= src(目标在前,源在后):按顺序从前向后拷贝,不会冲突。 - 若
dest > src(目标在后,源在前):逆序从后向前拷贝,避免源头被污染。
void* my_memmove(void* dest, const void* src, size_t count)
{
void* ret = dest;
if (dest < src)
{
// 正向拷贝
while (count--) { *(char*)dest = *(char*)src; dest=(char*)dest+1; src=(char*)src+1; }
}
else
{
// 反向拷贝,防止重叠覆盖
while(count--) { *((char*)dest + count) = *((char*)src + count); }
}
return ret;
}
第15步:内存比对(memcmp)
原型:int memcmp ( const void * ptr1, const void * ptr2, size_t num );
逻辑:比较两块内存的前 num 个字节。同样返回差值(正/负/0),常用于比较二进制结构体是否一致。
以上就是深入了解C语言中的字符串和内存函数的详细内容,更多关于C语言字符串 内存函数的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









