【C语言学习】字符串函数

🐱作者:一只大喵咪1201
🐱专栏:《C语言学习》
🔥格言:你只管努力,剩下的交给时间!
请添加图片描述

⚽strlen

size_t strlen ( const char * str );

参数类型是const修饰的char*类型的指针变量,返回类型是size_t,也就是unsigned int类型的变量。

  • 该函数的功能是统计字符串中字符的长度
#include <stdio.h>
#include <string.h>

int main()
{
	char arr1[] = "abcdef";
	int ret = strlen(arr1);
	printf("%d\n", ret);

	return 0;
}

图
打印出的结果就是字符串的长度6。

int main()
{
	char arr2[] = { 'a','b','c','d','e','f' };
	int ret = strlen(arr2);
	printf("%d\n", ret);

	return 0;
}

图
此时的结果就是19。

  • 两段代码中只有字符串初始化的形式不一样,初始化的内容都是abcdef,但是结果却不一样
    图
    这便是两种方式在内存中的样子,arr1与arr2的区别就在于一个又\0,一个没有\0

此时我们便可以知道,strlen库函数是从字符串的起始地址开始统计字符,直到字符结束标志\0才结束统计。

int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abcdef";
	
	if (strlen(arr1) - strlen(arr2) > 0)
		printf(">\n");
	else if (strlen(arr1) - strlen(arr2) < 0)
		printf("<\n");
	else
		printf("=\n");

	return 0;
}

图
arr1有4个字符,arr2有6个字符,但是该程序的结果却是大于,不是小于,这是什么原因呢?

  • strlen库函数的返回类型是unsigned int类型的,俩个无符号的整型相减,得到的结仍然是一个无符合的整型
  • -2在内存中的存储是11111111111111111111111111111110
  • 但此时CPU认为它是一个无符号的整型,并不是一个负数,所以它是一个正数,所以结果也就是大于

⚾模拟实现strlen

size_t my_strlen(const char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		str++;
		count++;
	}
	return count;
}

int main()
{
	char arr1[] = "abcdef";
	int ret = my_strlen(arr1);
	printf("%d\n", ret);

	return 0;
}

图
与strlen库函数的结果一致,这里采用的是计数器的方式实现的。

⚽strcpy

char * strcpy ( char * destination, const char * source );

该函数的参数类型是俩个char类型的指针,第一个指向被覆盖的空间,第二个指向覆盖字符串所在的空间,返回类型也是一个char类型的指针,指向的是被覆盖空间的起始地址。

该库函数的功能是将一个字符串复制到另一个字符串中

int main()
{
	char arr1[] = "xxxxxxxxxxxxxxx";
	char arr2[] = "abcdef";
	strcpy(arr1, arr2);

	return 0;
}

图

  • 通过调试我们可以看到,该函数将arr2中的字符串完全拷贝到了arr1中,并且将字符结束标志\0一同拷贝了过来。
  • strcpy是属于没有限制条件的拷贝,它拷贝的内容只有遇到字符结束标志\0才会结束。
int main()
{
	char* p = "abcdef";
	char arr[] = "world";
	strcpy(p, arr);

	return 0;
}

图
此时便会发生写入异常的错误。

  • 指针变量p中存放的是字符串abcdef首字符的起始地址,但是这个地址是在常量区的
  • 在使用strcpy函数的时候,无法将arr中的字符串覆盖的常量区的内存空间中,因为该空间中的内容是无法修改的。

⚾模拟实现strcpy

#include <assert.h>

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* start = dest;

	while (*dest++ = *src++);
	return start;
}

int main()
{
	char arr1[] = "xxxxxxxxxxxxxxx";
	char arr2[] = "abcdef";
	my_strcpy(arr1, arr2);

	return 0;
}

图
从调试中可以看到,和strcpy库函数的达到的效果是一样的。

⚽strcat

char * strcat ( char * destination, const char * source );

该函数是字符串追加函数,参数是俩个char类型的指针变量,第一个指针指向的是需要追加的字符串,第二个指针指向的是追加内容的字符串,返回类型是一个char类型的指针变量,该指针指向需要追加字符串的起始地址。

int main()
{
	char arr1[10] = "abcd";
	char arr2[] = { 'e','f','g','\0','x' };
	strcat(arr1, arr2);

	return 0;
}

图
这是没有追加时俩个数组中的内容。
与
这是追加后俩个数组中的内容。

  • 通过对比追加前后数组中的内容,我们发现:
  • arr1数组中字符结束标志\0被arr2中的字符e覆盖了
  • arr2中的字符串被追加到了arr1字符串的后面,但是只到\0就结束了

此时我们便可以得出结论,strcat函数是从被追加字符串的字符结束标志\0处开始追加的,将追加字符串的内容从此处开始复制过来,包括\0。

int main()
{
	char arr1[20] = "hello ";
	strcat(arr1, arr1);
	printf("%s\n", arr1);

	return 0;
}

图
但是不可以自己给自己追加字符串。

在追加的过程中,字符串中的\0会被覆盖掉,此时便找不到结束的位置。

⚾模拟实现strcat

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* start = dest;
	while (*dest != '\0')
	{
		dest++;
	}
	while (*dest++ = *src++);
	return start;
}
int main()
{
	char arr1[10] = "abcd";
	char arr2[] = { 'e','f','g','\0','x' };
	my_strcat(arr1, arr2);

	return 0;
}

图
可以看到,结果是与strcat一样的。

⚽strcmp

字符串的比较必须使用库函数来比较,不能像数字那样直接用大于小于号来比较。

int strcmp ( const char * str1, const char * str2 );

该函数的俩个参数是char*类型的指针变量,分别指向两个要比较的字符串,返回类型是int类型的整数。
图

  • 第一个字符串小于第二个字符串的时候,返回小于0的数
  • 第一个字符串等于第二个字符串的时候,返回值为0
  • 第一个字符串大于第二个字符串的时候,返回大于0的数
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abcd";
	int ret1 = strcmp(arr1, arr2);
	char arr3[] = "abcd";
	char arr4[] = "abcq";
	int ret2 = strcmp(arr3, arr4);
	printf("%d\n", ret1);
	printf("%d\n", ret2);

	return 0;
}

图
ret1的值是0,ret2的值是-1

  • ret1接收的是arr1和arr2的比较结果,我们可以看到,它们的内容是一样的,所以结果是0
  • ret2接收的是arr3和arr4的比较结果,我们可以看到,它们的长度是一样,不一样的地方只有字符d和字符q
  • 字符d的ASCII码值比字符q的ASCII码值小,所以返回一个负数图
  • 俩个字符串都从字符a开始比较,当俩个字符一样的时候,继续比较下一个,直到找到不一样的字符
  • 将它们的ASCII码值做差,如果是负数则返回一个小于0的数,如果是整数返回一个大于0的数。
int main()
{
	char arr1[] = "zhangsan";
	char arr2[] = "zhangsanfeng";
	int ret = strcmp(arr1, arr2);
	if (ret > 0)
		printf(">\n");
	else if (ret == 0)
		printf("==\n");
	else
		printf("<\n");

	return 0;
}

图
通常我们都是使用它的返回值来实现一些逻辑关系。

⚾模拟实现strcmp

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while ((*str1 == *str2))
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[] = "zhangsan";
	char arr2[] = "zhangsanfeng";
	int ret = my_strcmp(arr1, arr2);
	if (ret > 0)
		printf(">\n");
	else if (ret == 0)
		printf("==\n");
	else
		printf("<\n");

	return 0;
}

图
可以看到,与strcmp库函数的结果一样。

⚽strncpy

char * strncpy ( char * destination, const char * source, size_t num );

该函数和strcpy的功能一样,也是复制字符串的,但是它有字符的限制。它的有三个参数,第三个参数是要复制字符串的字节数。

int main()
{
	char arr1[20] = "helloxxx";
	char arr2[] = "world";
	strncpy(arr1, arr2, 6);

	return 0;
}

图
可以看到,将arr2中的六个字符复制到了arr1中,从arr1的第一个字符开始覆盖的。

  • 第3个参数输入几就会复制几个字节的数据
  • 输入6的时候将arr2中的\0也复制了过去

⚾模拟实现strncpy

char* my_strncpy(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* start = dest;
	while (num--)
	{
		*dest = *src;
		dest++;
		src++;
	}
	return start;
}
int main()
{
	char arr1[20] = "helloxxx";
	char arr2[] = "world";
	my_strncpy(arr1, arr2, 6);

	return 0;
}

图
可以看到,和strncpy的结果是一样的。

⚽strncat

char * strncat ( char * destination, const char * source, size_t num );

该函数同样和strcat的功能一样,但是它可以限制追加的字符,其中第三个参数就是设置追加的字符个数的。

int main()
{
	char arr1[20] = "hello\0xxxxx";
	char arr2[] = "world";
	strncat(arr1, arr2, 5);
	return 0;
}

图
追加以前,arr1数组中的内容如上。
图
追加以后数组中的内容。

  • 同样也是从\0处开始追加,只有要遇到\0便开始追加,并不一定是字符串最后的\0处
  • 设置追加的字节数是5,就只追加5个字节,也就是world,其中\0并没有追加过去

⚾模拟实现strncat

char* my_strncat(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* start = dest;
	while (*dest)
	{
		dest++;
	}
	while (num--)
	{
		*dest = *src;
		dest++;
		src++;
	}
	return start;
}
int main()
{
	char arr1[20] = "hello\0xxxxx";
	char arr2[] = "world";
	my_strncat(arr1, arr2, 5);
	return 0;
}

图
可以看到,和strncat的结果是一样的。

⚽strncmp

int strncmp ( const char * str1, const char * str2, size_t num );

该函数同strcmp一样,也是比较字符串的,但是它可以限制比较的个数,通过第三个参数来设置。

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcqef";
	int ret1 = strncmp(arr1, arr2, 3);
	int ret2 = strncmp(arr1, arr2, 6);
	printf("ret1 = %d\n", ret1);
	printf("ret2 = %d\n", ret2);

	return 0;
}

图

  • 当比较的字节数设置成3的时候,俩个数组都是比较前三个字符,都是abc,所以是相等的,返回数值0
  • 当比较的字节数设置成6的时候,俩个数值都是比较前六个字符,在逐个比较的过程中,当arr1中的字符d和arr2中的字符q比较的时候,便出现了差异
  • 字符d的ASCII码值小于字符q的ASCII码值,所以返回值是负数。

⚾模拟实现strncmp

int my_strncmp(const char* str1, const char* str2, size_t num)
{
	assert(str1 && str2);
	while (num--)
	{
		if (*str1 == *str2)
		{
			if (*str1 == '\0')
				return 0;
			str1++;
			str2++;
		}
		else
			return *str1 - *str2;
	}
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcqef";
	int ret1 = my_strncmp(arr1, arr2, 3);
	int ret2 = my_strncmp(arr1, arr2, 6);
	printf("ret1 = %d\n", ret1);
	printf("ret2 = %d\n", ret2);

	return 0;
}

图

  • 比较三个字节的时候,结果与strncmp一样,都是0
  • 比较6个字节的时候,strncmp是-1,my_strcmp是-13
  • 该函数的返回值是,只要第一个字符串小于第二个,返回一个负数,至于这个数是多少并没有规定,而在本喵使用的VS2019中使它是-1,在别的编译器中不一定返回是-1

⚽strstr

const char * strstr ( const char * str1, const char * str2 );

该函数的作用是在一个字符串中寻找另一个字符串。俩个参数是char类型的指针变量,str1指向的是大的字符串,str2指向的是要寻找的字符串。返回类型同样是一个char类型的指针变量。

  • 如果找到要找的字符串,则返回str2字符串首字符在str1字符串中所在的地址
  • 如果没有找到则返回一个空指针NULL
int main()
{
	char arr1[] = "wxf786808661@163.com";
	char arr2[] = "@163";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
		printf("字串不存在\n");
	else
		printf("%s\n", ret);
	
	return 0;
}

图

  • 在字符串wxf786808661@163.com中寻找字符串@163
  • 我们可以看到是可以找的到的,所以返回字符@在arr1字符串中的地址
  • 最后的打印就是从该地址开始的

当然,如果将arr2中的内容改变

int main()
{
	char arr1[] = "wxf786808661@163.com";
	//char arr2[] = "@163";
	char arr2[] = "5984";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
		printf("字串不存在\n");
	else
		printf("%s\n", ret);
	
	return 0;
}

图

⚾模拟实现strstr

首先我们需要来分析一下它的逻辑过程
图

  • 指针s1指向大字符串首字符的地址
  • 指针s2指向目标字符串首字符的地址
  • 将s1和s2所指向空间中的内容做比较
  • s1指向的内容与s2指向的内容不相等,s1指针继续往回移动,s2指针不动
  • 再继续比较俩个指针所指向的内容
  • 直到s1指向的内容是字符@的时候,此时和s2指向的内容是一样的
  • 指针s1和指针s2同时向后移动并且做比较
  • 直到指针s2指向字符结束标志\0的时候,在字符@和字符\0之间,s1和s2所指向的内容都相同,此时将arr1中字符@的地址返回
  • 如果在@和\0之间s1和s2指向的内容有不一样的时,之间返回空指针

以上就是我们举例代码的逻辑。
那么当遇到这样的俩个字符串时
图
就无法使用上面的逻辑了

  • 第一个字符b可以找到
  • arr1中字符b后面的字符还是b
  • arr2中字符b后的字符就成了c了
  • 此时俩个字符是不相等的,按照上面的逻辑我会之间返回空指针的

我们只要对上面的逻辑做一些改进变可以满足这种情况。
图

  • 同样s1是从字符a开始与s2指向的内容比较的
  • 当s1和s2指向的内容不同的时候,s1往后移动,s2不动
  • 当s1移动到和s2指向的内容相同的时候,将s1的地址放在另一个指针p中
  • s1和s2同时向后移动并且进行比较,直到遇到s2指向的\0结束比较

图

  1. 这个比较期间,如果存在s1和s2指向的内容不同,则s1指向p所指向地址的下一个地址,s2指向字符串最开始的位置重新进行一轮上面的比较,直到p指向s1中的\0
  2. 如果在这个比较期间,s1和s2指向的内容始终相同,直到s2指向\0,那么就返回arr2中首字符在arr1中的地址
char* my_strstr(const char* str1, const char* str2)
{
	const char* s1 = str1;
	const char* s2 = str2;
	const char* p = str1;

	while (*p)
	{
		s1 = p;
		s2 = str2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return (char*)p;
		p++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "abbbcd";
	//char arr2[] = "@163";
	char arr2[] = "bcd";
	char* ret = my_strstr(arr1, arr2);
	if (ret == NULL)
		printf("字串不存在\n");
	else
		printf("%s\n", ret);
	
	return 0;
}

图可以看到,我们模拟的函数是可以找到这个子字符串的。

⚽strtok

char * strtok ( char * str, const char * delimiters );

该函数是用来分割字符串的。两个参数都是char*类型的指针变量,其中第一个指针变量指向的是被分割的字符串,第二个指针变量指向的是分隔符集合的字符串。返回类型是一个指针变量,指向的是被分割字符串的起始地址。

int main()
{
	char arr1[] = "wxf786808661@163.com";
	char arr2[] = "@.";
	char* ret = NULL;//接收返回地址

	ret = strtok(arr1, arr2);
	printf("%s\n", ret);
	//从第二个分割符开始,第一个实参是空指针
	ret = strtok(NULL, arr2);
	printf("%s\n", ret);
	return 0;
}

图

  • 分割符是字符@和字符.
  • 所以会将字符串分割成wxf786808661和163俩个字符串
  • 字符@分割的时候,strtok的俩个实参分别是arr1和arr2,在arr1中找到字符@以后,将其改成了\0,并且记录了该地址,返回arr1的起始地址。
  • 字符.分割的时候,strtok的俩个实参分别是NULL和arr2,在arr1中找到字符.以后,将其改成了\0,并且记录该地址,上面那个\0所在的地址
int main()
{
	char arr1[] = "wxf786808661@163.com";
	char arr2[] = "@.";
	char* ret = NULL;//接收返回地址

	ret = strtok(arr1, arr2);
	printf("%s\n", ret);
	//从第二个分割符开始,第一个实参是空指针
	ret = strtok(NULL, arr2);
	printf("%s\n", ret);
	ret = strtok(NULL, arr2);
	printf("%s\n", ret);
	ret = strtok(NULL, arr2);
	printf("%s\n", ret);
	return 0;
}

图

  • 第一个打印语句打印字符@之前的字符串
  • 第二个打印语句打印字符.之前的字符串
  • 第三个打印语句打印字符\0之前的字符串
  • 第四个打印语句由于没有分割符,所以会返回一个空指针
int main()
{
	char arr1[] = "wxf786808661@163.com wxf786808661@163.com wxf786808661@163.com";
	char arr2[] = "@. ";
	char* ret = NULL;//接收返回地址

	for (ret = strtok(arr1, arr2); ret != NULL; ret = strtok(NULL, arr2))
		printf("%s\n", ret);

	return 0;
}

图
当分割成很多个字符串的时候,需要通过循环的方式打印出来。

⚽strerror

char * strerror ( int errnum );

C语言的库函数在执行失败的时候会设置错误码,例如0 1 2 3 4 5 6 7 8,这些错误码每一个都有它具体的意义,但是在执行过程中不会告诉我们具体是什么意义,此时就需要使用这个函数去看具体的意义。

  • 实参需要传入的就是错误码
  • 返回类型是一个char*类型的指针变量,指向存放具体错误信息的内存空间。
int main()
{
	int i = 0;
	for (i = 0; i < 9; i++)
	{
		printf("%d:%s\n", i, strerror(i));
	}
	return 0;
}

图
这是打印了一些错误码对应的错误信息。

#include <errno.h>

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		printf("成功打开\n");
	}
	return 0;
}

图

  • 在工程里本喵并没有创建test.txt文件,所以肯定是找不到的,这个时候打开就发生了错误,它会产生一个错误码
  • 专门有一个全局变量errno来存放这些产生的错误码,但是必须引用头文件errno.h才能使用这个全局变量
  • 将变量值传给strerror函数便可以得到具体的错误信息。

⚽字符分类函数

函数如果它的参数符合下列条件就返回真
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit十进制数字 0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母a ~ f,大写字母A~F
islower小写字母a~z
isupper大写字母A~Z
isalpha字母a~ z或A~Z
isalnum字母或者数字,az,AZ,0~9
ispunct标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符
#include <ctype.h>

int main()
{
	int a1 = isspace(' ');
	printf("%d\n", a1);

	int a2 = isdigit('x');
	printf("%d\n", a2);
	
	return 0;
}

图
这是这些函数的一部分使用案例。

  • 符合条件返回一个不为0的数,也就是真值
  • 不符合条件返回0,也就是假

⚽字符转换函数

int tolower ( int c );
int toupper ( int c );

字符转换函数只有这俩个。

#include <ctype.h>
int main()
{
	int i = 0;
	char str[] = "Test String.\n";
	char c;
	while (str[i])
	{
		c = str[i];
		if (isupper(c))
			c = tolower(c);
		putchar(c);
		i++;
	}
	return 0;
}

图
该代码就是将字符串中的大写字母改成小写。

⚽总结

和字符串相关的库函数还有很多,本喵只是挑选了一些比较常用的加以介绍,如果遇到其他陌生的函数可以去Cplusplus或者MSDN软件查看库函数的使用方法以及参数类型。

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只大喵咪1201

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值