C语言字符串、字符与内存函数详解及模拟实现

阿澈
阿澈
发布时间:2026-10-16| 4人浏览过
原创

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语言字符串的资料请关注本站其它相关文章!
相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。