首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C语言字符串函数:教科书不敢告诉你的那些隐藏用法和逆天技巧

C语言字符串函数:教科书不敢告诉你的那些隐藏用法和逆天技巧

作者头像
码途随笔
发布2026-01-12 19:52:22
发布2026-01-12 19:52:22
770
举报

一、字符函数

1.1 字符分类与字符转换

两种函数都需要包含头文件ctype.h

  • 字符分类函数

都是相同的使用方式:

代码语言:javascript
复制
	int ret = islower('A');

islower()是能够判断参数部分是否为小写字母,通过返回值来判断,如果是小写,返回非零,不是零,放回0 其他的函数同理,也是通过返回值确定

  • 字符转换 在C语言中有两个字符转换函数,通过函数将参数转换为大/小写
代码语言:javascript
复制
	int tolower(int c); //	将参数传进去的大写字⺟转小写
	int toupper(int c); //	将参数传进去的小写字⺟转大写
  • 综合练习
代码语言:javascript
复制
#include<ctype.h>
#include<stdio.h>
int main()
{
	char arr[] = "i AM stUdent";
	int i = 0;
	while (arr[i] != '\0')
	{
		if (islower(arr[i]))
		{
			arr[i] = toupper(arr[i]);
		}
		i++;
	}
	printf("%s", arr);
	return 0;
}

二、函数介绍及模拟实现

以下函数都必须包含头文件<string.h>

2.1 strlen

从传递的地址开始,统计’\0’之前的个数 注意事项:返回值的类型是size_t

代码语言:javascript
复制
int main()
{
	if ((strlen("abc") - strlen("abcdef"))>0)
	{
		printf("haha");
	}
	else
	{
		printf("hehe");
	}
	return 0;
}

运行的结果是“haha”,结果虽然是-3,但是-3当成无符号数,前面的负号不再是符号位了而是有效位了,是一个非常大的正数 模拟实现

  • 方法1:计数器
代码语言:javascript
复制
int main()
{
	char arr[] = "abcdef";
	int count = 0;
	char* start = arr;
	while (*start != '\0')
	{
		start++;
		count++;
	}
	printf("%d", count);
	return 0;
}

方法2:指针相减

代码语言:javascript
复制
int main()
{
	char arr[] = "abcdef";
	char* start = arr;
	while (*start != '\0')
	{
		start++;
	}
	printf("%d", start-arr);
	return 0;
}

方法3:递归

代码语言:javascript
复制
size_t my_strlen(const char * p)
{
	if (*p == '\0')
	{
		return 0;
	}
	else
	{
		return 1 + my_strlen(p + 1);
	}
}
int main()
{
	char arr[] = "abcdefg";
	size_t ret = my_strlen(arr);
	printf("%d", ret);
	return 0;
}

2.2 strcpy

易错点:arr1=arr2,数组名是首元素地址,是常量值,不能直接赋值,要用strcpy函数

把源头的数据拷贝到目的地空间里面去,目标空间足够大,能放得下从源头拷贝过来的数据,目标空间必须可修改(比如不能是常量字符串)

具体操作:

如何验证\0是否拿下来了?

通过调试

模仿实现

  • 方法1(初级):
代码语言:javascript
复制
void my_strcpy(char *p2,char *p1)
{
	while (*p1 != '\0')
	{
		*p2 = *p1;
		p1++;
		p2++;
	}
	*p2 = *p1;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "***********";
	my_strcpy(arr2, arr1);
	printf("%s", arr2);
	return 0;
}
  • 方法2:
代码语言:javascript
复制
char* my_strcpy(char* p2, const char* p1)
{
	char* ret = p2;
	while (*p2++ = *p1++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "***********";
	char* ret = my_strcpy(arr2, arr1);
	printf("%s", ret);
	return 0;
}

2.3 strcat

注意事项:目标地址最好初始化(防止空间不够,因为未初始化的话,默认按照后面的元素来判断) 两字符串数组必须要有\0,并且目标空间可修改

模拟实现:

代码语言:javascript
复制
#include<string.h>
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* p1, const char* p2)
{
	assert(p1 && p2);
	char* ret = p1;
	while (*p1 != '\0')
	{
		p1++;
	}
	while (*p1++ = *p2++);
	return ret;
}
int main()
{
	char arr1[20] = "abcdef";
	char arr2[] = "******";
	char* ret = my_strcat(arr1, arr2);
	printf("%s", ret);
	return 0;
}
}

可以实现自己与自己追加吗?

不能,因为找不到结束的\0,被赋值的永远走在前面,陷入死循环,越界访问,最后崩溃

2.4 strcmp

一个一个字符比较,不能拿数组名和字符串(代表的也是首元素的地址)去比较

注意:在VS上返回的是0、1、-1这三种值,其他编译器只要是正数或负数就行了 模拟实现:

代码语言:javascript
复制
int my_strcmp(const char* p1, const char* p2)
{
	assert(p1 && p2);
	while (*p1 == *p2)
	{
		if (*p1 == '\0')
		{
			return 0;
		}
		p1++;
		p2++;
	}
	return *p1 - *p2;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcaqw";
	int ret =my_strcmp(arr1, arr2);
	printf("%d", ret);
	return 0;
}

2.5 strstr

在一个字符串中查找另一个字符串是否出现过,如果出现过,返回的是另一个字符串在另一个字符串第一次出现过的位置,如果找不到(没出现),则返回空指针

代码语言:javascript
复制
int main()
{
	char arr[] = "this is bit";
	char* p = "Is";
	char * ret = strstr(arr, p);
	if (ret != NULL)
	{
		printf("%s", ret);
	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

函数模拟

  • 第一种情况
在这里插入图片描述
在这里插入图片描述
  • 第二种情况
在这里插入图片描述
在这里插入图片描述
  • 第三种情况
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
char* Fuc(const char* p1, const char* p2)
{
	const char* s1 = NULL;
	const char* s2 = NULL;
	char* s3 = p1;
	if (p2 == NULL)
	{
		return (char*)p1;
	}
	while (*s3 != '\0')
	{
		s1 = s3;
		s2 = p2;
		while (*s1!='\0'&&*s2!='\0'&& * s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)s3;
		}
		s3++;
	}
	return NULL;
}
int main()
{
	char arr[] = "this is dert";
	char* p = "is";
	char *ret = Fuc(arr, p);
	printf("%s", ret);
	return 0;
}

三、函数使用

3.1 strncpy

会拷贝\0进去吗?

该函数是长度受限制的字符串函数,让拷贝几个就拷贝几个,不会拷贝\0 验证:

参数个数太大怎么办?

strncpy()比较看重参数个数,即使数组里面的个数不够那么多数,也让\0去凑

3.2 strncat

会追加\0进去吗?

要有测试的思维,直接追加(最后都有\0,看不出来),所以让它提前追加(自己放个\0进去),因为是从第一个\0开始追加的,事实证明:会把\0追加进去,保证追加过去是一个字符串

追加的个数大于字符串本身会怎么样?会不会像strncpy一样补充\0?

不会,当把所有字符传过去(包括\0),就不会再管了

3.3 strncmp

strcmp多了一个参数,最大比较的个数,如果前几个数比较出来大小的话,就知道结果了(后面不用比较了),多了一个参数确定比较的大小

3.4 strtok

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

会把原始数据改掉,最好是再拷贝一份设置为函数的参数,第一次传参传的是你要切割的那个字符串,第二次传的是空指针(会从保存好的地址向后查找)

代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "3456@qq.com";
	char* p = "@.";
	char arr1[20] = { 0 };
	strcpy(arr1, arr);
	char* ret = NULL;
	for (ret = strtok(arr, p); ret != NULL; ret = strtok(NULL, p))
	{
		printf("%s\n", ret);
	}
	return 0;
}

3.5 strerror

在这里插入图片描述
在这里插入图片描述

打印错误码

代码语言:javascript
复制
int main()
{
	for (int i = 0; i <= 10; i++)
	{
		printf("%d = %s\n",i, strerror(i));
	}
	return 0;
}
在这里插入图片描述
在这里插入图片描述

应用场景(举例): 因为errno里面存放的是错误码,可以直接传给strerror这个函数

代码语言:javascript
复制
int main()
{
	FILE* ret =fopen("test.txt", "r");
	if (ret == NULL)
	{
		printf("%s", strerror(errno));
	}
	return 0;
}

扩展:perror

perror函数比strerror就更简单了 用法: 先打印传给函数的字符串,在打印冒号与空格,最后打印错误信息

在这里插入图片描述
在这里插入图片描述

当给空字符串时,就什么都不打印,只打印错误信息(给字符串就说明可以加入一些自定义的信息,如果不给,就只把错误信息打印出来)

在这里插入图片描述
在这里插入图片描述

总结: 当想直接把错误码对应的错误信息打印出来可以用perror,想获得错误码对应的错误信息就用strerror

四、内存操作函数

4.1 memcpy

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7 };
	int arr1[20] = { 0 };
	memcpy(arr1, arr, 16);
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

模拟实现:

代码语言:javascript
复制
#include<string.h>
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* p1, void* p2, int n)
{
	void* ret = p1;
	assert(p1 && p2);
	while (n--)
	{
		*(char*)p1 = *(char*)p2;
		p1 = (char*)p1+1;
		p2 = (char*)p2+1;
	}
	return ret;
}
int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int arr1[20] = { 0 };
	my_memcpy(arr1, arr, 16);
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

注意:两数组内存不能够重叠,虽然memcpy函数可以实现内存相同的数组拷贝,但是我么们不指望它实现该功能

那谁适合于内存重叠的拷贝呢?

4.2 memmove

在这里插入图片描述
在这里插入图片描述

用法:

代码语言:javascript
复制
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr + 2, arr, 5 * sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

模拟实现的分析思路: 如果再创造一个数组的话,不仅要把数据拷贝过来,还要把拷贝完的数组再传回去,太麻烦了,既然从前面拷贝不行就从后面拷贝,但是不是所有情况都适合从后往前拷

在这里插入图片描述
在这里插入图片描述

具体过程:

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
void* my_memmove(void* p1, void* p2, size_t num)
{
	if (p2 > p1)
	{
		while (num--)
		{
			*(char*)p1 = *(char*)p2;
			p1 = (char*)p1 + 1;
			p2 = (char*)p2 + 1;
		}

	}
	else
	{
		while (num--)
		{
			*((char*)p1 + num) = *((char*)p2 + num);
		}
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr, arr+3, 5 * sizeof(int));
	for (int i = 0; i<10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

4.3 memset

在这里插入图片描述
在这里插入图片描述

基本用法:

代码语言:javascript
复制
int main()
{
	char arr[] = "abcdef";
	memset(arr, 'x', 3);
	printf("%s", arr);
	return 0;
}

易错点:它是一个一个字节来设置的,他会把每一个字节都填充给想要的值,而不是说把每一个元素给改了(不是以元素为单位设置的

在这里插入图片描述
在这里插入图片描述

4.4 memcmp

在这里插入图片描述
在这里插入图片描述

注意:该函数是一个一个字节比较的 比如:比到第17个字节时就停止比较了(提前得到结果就确定大小了),与strcpy比较

在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、字符函数
    • 1.1 字符分类与字符转换
  • 二、函数介绍及模拟实现
    • 2.1 strlen
    • 2.2 strcpy
    • 2.3 strcat
    • 2.4 strcmp
    • 2.5 strstr
  • 三、函数使用
    • 3.1 strncpy
    • 3.2 strncat
    • 3.3 strncmp
    • 3.4 strtok
    • 3.5 strerror
  • 四、内存操作函数
    • 4.1 memcpy
    • 4.2 memmove
    • 4.3 memset
    • 4.4 memcmp
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档