深入理解C语言字符串与内存操作函数

木白
木白
发布时间:2026-10-15 | 7人浏览过
原创

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);

操作规范:

  1. 自动追加尾部:不仅复制内容,还会连同源串的 '\0' 一起写入目标空间。
  2. 容量要求:destination 指向的空间必须足够大,且为可修改状态。
  3. 返回值:返回目标空间的起始地址,便于链式调用。

安全风险提示:若目标空间不足,将引发缓冲区溢出(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 );

执行逻辑:

  1. 首先定位 destination 中的 '\0' 作为新的写入起点。
  2. 将 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 );

特点:

  1. 按 byte 为单位进行连续复制。
  2. 不会在遇到 '\0' 时停止。
  3. 严重约束:源区与目标区绝对不能重叠,否则结果未定义。
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语言字符串 内存函数的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表