-字符串函数(2)

目录

strerror


如何比较两个字符串的内容是否相等:

我们先创建代码进行尝试:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	char arr1[20] = "zhangsan";
	char arr2[20] = "zhangsanfeng";
	if (arr1 == arr2)
	{
		printf("==");
	}
	else
	{
		printf("!=");
	}
	return 0;
}

我们进行编译

答案是不相等,很多人就有疑问:两个字符串的确不相等啊,所以我们写的代码是对的。

究竟是对的吗?我们再进行尝试

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	char arr1[20] = "zhangsan";
	char arr2[20] = "zhangsan";
	if (arr1 == arr2)
	{
		printf("==");
	}
	else
	{
		printf("!=");
	}
	return 0;
}

 我们把两个字符串内容写成相等的,再进行编译

 可以发现,依旧是不相等,原因是什么呢?

答:arr1和arr2都是数组名,数组名表示首元素的地址,这两个数组的首元素并不是在同一个空间,所以arr1!=arr2.

我们可以发现,我们用这种方法进行比较的时候,压根比的不是字符串的内容,而是比的数组首元素的地址,所以这种方法肯定是错的。

那么究竟该如何比较两个字符串相等呢?

我们引出函数strcmp

 strcmp函数的两个参数是两个字符指针,这两个字符指针对应的就是字符串首字符的地址,返回值是整型,这个返回值是什么呢?

 由图标可知,当第一个字符串小于第二个字符串,返回小于0的数字

当第一个字符串等于第二个字符串,返回等于0的数字。

当第一个字符串大于第二个字符串,返回大于0的数字。

我们可以这样书写

#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<stdio.h>
int main()
{
	char arr1[20] = "zhangsan";
	char arr2[20] = "zhangsanfeng";
	int ret = strcmp(arr1, arr2);
	if (ret < 0)
		printf("<\n");
	else if (ret == 0)
		printf("=\n");
	else
		printf(">\n");
	return 0;
}

这里我们有一个疑问?

字符串是如何比较大小的呢?

注意:字符串比较大小的时候,比较的是内容而不是字符串的长度,例如:zhangsan和zhangsanfeng进行比较,首先比较各自的第一位,都为z,相等,再比较各自的第二位,都为h,相等-------,直到n,都相等,比较下一位,zhangsan的字符串的下一位是\0,而zhangsanfeng的字符串的下一位是f,f对应的ascii码值大于zhangsan,所以对应字符串zhangsanfeng就大于zhangsan。

我们再举一个例子,比如abcdef和abq进行比较

前两位都相等,比较第三位,因为q对应的ascii码值大于c,所以字符串abq大于字符串abcdef。

接下来,我们来模拟实现strcmp函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char*str1, const char*str2)
{
	assert(str1&&str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	if (*str1 > *str2)
		return 1;
	else
		return -1;
}
int main()
{
	char arr1[20] = "zhangsan";
	char arr2[20] = "zhangsanfeng";
	int ret = my_strcmp(arr1, arr2);
	if (ret < 0)
		printf("<\n");
	else if (ret == 0)
		printf("=\n");
	else
		printf(">\n");
	return 0;
}

第一个字符串小于第二个字符串,所以应该是小于号

我们对代码提出一些问题:

1:const char*str1, const char*str2)为什么两个字符指针都需要加上const

答:因为我们只是要比较字符串而已,所以不想让字符串内容发生改变,让代码更加健壮。

2:assert(str1&&str2);断言函数有什么目的?

答:在进行解引用之前,一定要加上断言函数,断言函数能够防止空指针的出现,空指针不能解引用,否则直接报错。

3:为什么main函数的if else语句有三部分,而函数定义中的if else语句只有两部分

答:因为函数定义部分中的一个语句*str1==*str2在函数定义的开端,也就是说,函数只要能够运行到if else语句,就没有相等的可能性了。

我们可以对代码进行简化:我们直接删除函数定义部分的if else语句,直接返回两个字符串指针解引用的差值

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

如图所示

strcpy

strcmp

strcat

这些函数都是长度不受限制的字符串函数,使用时会造成一些问题,例如

int main()
{
	char arr1[4] = { 0 };
	strcpy(arr1, "hello bit");
	printf("%s", arr1);
	return 0;
}

我们可以发现,我们的数组arr1只能存放四个元素,但是我们把hello bit整个都存放进去,正常情况下是存放不进去的,我们进行编译

 代码也的确报错了,但是我们的显示器上依旧打印了”hello bit“,说明这个字符串已经拷贝,数组明明放不下,但是字符串依旧拷贝,造成代码越界访问,这就是这个函数的问题。

这是我们加入#define _CRT_SECURE_NO_WARNINGS 1的结果,假如我们把他屏蔽掉

 程序报错,并且提示我们用strcpy_s函数,虽然这个函数相对于strcpy更加安全,但是这个函数只能够在vs上使用,所以没有可移植性,所以我们不采纳。

上面的三个函数都是长度不受限制的字符串函数,接下来,我们写一些长度受限制的字符串函数

strncpy

strncat

strncmp

我们举一个例子

我们先使用strncpy函数

 我们可以发现:strncpy函数比strcpy函数的参数多了一个整型,我们写一个strncpy函数的代码

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abcdef";
	char arr2[] = "hello bit";
	strncpy(arr1, arr2, 5);
	printf("%s", arr1);
	return 0;
}

这里的参数5的意思是只拷贝五个字符,我们进行编译

 strncpy实现了字符串拷贝,并且只拷贝了数组arr2[]的五个字符给数组arr1[]。

这时候,有人提出一个问题:假如我们的数组中的字符串只有三个字符,而我们却要拷贝五个会发生什么?

答:我们进行实验

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abcdef";
	char arr2[] = "bit";
	strncpy(arr1, arr2, 5);
	printf("%s", arr1);
	return 0;
}

我们进行编译

只打印了bit,原因是什么呢?我们进行调试解答。 

 我们可以发现,当数组元素不够时,函数会把\0拷贝到目的地数组中去,虽然我们的字符f并没有消失,但是我们提前拷贝了\0,字符串的结束标志是\0,所以只打印了bit。

接下来,我们分析一下strncat

 可以发现,strncat只是比strcat多了一个参数。

我们进行实验

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "bit";
	strncat(arr1, arr2, 3);
	return 0;
}

我们进行调试,

这是strncat函数调用完毕之后的结果 ,我们可以发现,bit追加到了数组arr1的内部,这里我们有一个问题,这个arr[9]对应的\0是怎么来的?是数组arr1自动添加的,还是strncat追加的?

答:我们进行实验

	char arr1[20] = "hell\0xxxxxxxx ";

我们把数组arr1[]修改成这样,我们的追加应该是覆盖了一个\0和两个x,我们进行调试

假如是数组arr1后面的\0,那么应该在x的最后面,所以这里的\0是函数strncat追加的,由此可得:strncat函数在追加时,会在字符串的末尾追加\0。 

那么假如我们数组arr2只有三个字符,但是我们调用strncat函数对arr1追加6个字符,是不是想strncpy一样追加\0呢?

答:我们进行试验

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "hell\0xxxxxxxx ";
	char arr2[] = "bit";
	strncat(arr1, arr2, 6);
	return 0;
}

当调用strncat函数之后

 我们可以发现,strncat并没有再多追加\0.

接下来,我们介绍一个strstr:查找子串的一个函数。

简单的说:给你一个字符串,从字符串中查找另外一个字符串。

 strstr函数的参数是两个字符指针,返回值也是一个字符指针。

 strstr函数是这样使用的

#include<stdio.h>
#include<string.h>
int main()
{
	char email[] = "zpw@bitejiuyeke.com";
	char substr[] = "bitejiuyeke";
	char*ret = strstr(email, substr);
	if (*ret == NULL)
		printf("字符串不存在\n");
	else
		printf("%s", ret);
	return 0;
}

strstr是这样作用的:看email数组中对应的字符串时候有substr数组对应的字符串,如果有的话,返回email对应的字符串中从substr字符串首元素开始的字符串,如果没有的话,返回空指针。

由代码可知,这emai字符串是包含substr对应的字符串的。我们进行运行

接下来,我们来模拟实现strstr函数

#include<stdio.h>
#include<string.h>
#include<assert.h>
char*my_strstr(const char*str1, const char*str2)
{
	assert(str1&&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 p;
		}
		p++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "bcd";
	char*ret = my_strstr(arr1, arr2);
	if (ret == NULL)
	{
		printf("字符串不存在\n");
	}
	else
	{
		printf("%s", ret);
	}
	return 0;
}

提出几个问题:while (*p)在这里有什么作用?

答:当p解引用是空指针的时候,就证明数组arr1里的字符串直到\0都没有匹配到数组arr2内的全部字符,所以就证明字符串不存在,当p解引用不是空指针,我们就进入循环。

2:*s1 != '\0'&&*s2 != '\0'&&*s1 == *s2为什么while循环的进入条件需要同时满足以上三个?

答:最简单的条件:*s1 == *s2因为我们while循环就是为了筛选字符串的,筛选的前提就是解引用后对应的字符必须相等。

*s1 != '\0'这个条件:如果s1解引用后结果等于\0,表示已经到第一个字符串的末尾也没有结束循环,所以该字符串不存在

*s2 != '\0'这个条件:假如s2解引用后结果等于\0,就表示数组arr2内部的字符串出现在arr1内部,所以该字符串满足条件,所以结束该循环。

下一个函数:strtok

 我们看这个函数的结构:参数是两个指针,第二个指针对应的汉语意思是分隔符,返回值是字符指针。

这个函数的意思是切割字符串,这个函数的实现步骤比较复杂。

1:delimiters参数是个字符串,用来定义分隔符的字符集合

2:第一个参数指定一个字符串,该字符串包含0个或多个由deli字符串中一个或多个分隔符分割的标记。

3:strtok会找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。

(strtok函数会改变字符串的内容,所以一般用strtok切割被拷贝过的字符串并且可以被修改。

4:strtok的第一个参数不是NULL,他会找到str中的第一个标记,然后保存它在字符串中的位置

5:strtok的第二个参数是NULL,函数将在同一个字符串被保存的位置开始,寻找他的下一个标记。

6:如果字符串中,不存在更多的标记,我们就返回空指针。

首先,我们分析第一个。

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1 = "zsk@1390281278.com";
	const char*sep = "@.";
}

sep就是我们设置的第二个参数,我们通过这个参数创建一个字符串,字符串内部的元素是我们自己设计的分割符。

第二个:

strtok(arr1, sep);

arr1就是我们对应的参数,这个参数指向字符串"zsk@1390281278.com",并且含有我们所设计的分割符。

第三个:

函数首先找到下一个标记@,把这个@改成\0,这时候就分割出来了一个字符串了,然后我们返回这个字符串首元素的地址,也就是z的地址。

因为strtok函数会改变字符串的内容,所以我们要对字符串进行拷贝,拷贝之后再调用函数

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1 = "zsk@1390281278.com";
	const char*sep = "@.";
	char sp[30] = { 0 };
	strcpy(sp, arr1);
	strtok(sp, sep);
}

意思就是这样:我们要调用strtok函数修改分割字符串,因为我们分割之后会改变字符串的内容,所以我们先拷贝一个字符串,用该字符串来调用函数来实现分割字符串,又不会改变字符串的内容。

4:第一个参数不是空指针,我们直接找到第一个分割符@,把@改成\0,分割出一个字符串,返回这个字符串首元素的地址,然后保存我们找到第一个分割符的位置。

5:第二个参数是空指针,我们要在我们之前保存的分割符的位置开始,寻找他的下一个标志。

6:运行到最后,没有分割符了,我们调用函数,返回的就是空指针了。

我们实现这个完整的函数

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "zsk@1390281278.com";
	const char*sep = "@.";
	char sp[30] = { 0 };
	strcpy(sp, arr1);
	char*ret=strtok(sp, sep);
	printf("%s\n", ret);
	ret=strtok(NULL, sep);
	printf("%s\n", ret);
	ret=strtok(NULL, sep);
	printf("%s\n", ret);
	return 0;
}

我们发现,这种写法效率太低,假如我们有大量的分割符在字符串中,我们不能一个语句一个语句的写,接下来,我们介绍一种for循环的方法解决这个问题。

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "zsk@1390281278.com";
	const char*sep = "@.";
	char sp[30] = { 0 };
	strcpy(sp, arr1);
	char*ret = NULL;
	for (ret = strtok(sp, sep); ret != NULL; ret = strtok(NULL, sep))
	{
		printf("%s\n", ret);
	}
	return 0;
}

我们在for循环语句这样写,首先执行ret = strtok(sp, sep),然后进行判断,假如ret != NULL,那么我们就printf("%s\n", sp)打印出分割的字符串,接下来我们再执行ret = strtok(NULL, sep),因为我们不是第一次调用函数,所以我们的第一个参数是空指针,然后一直执行循环,直到ret等于空指针时,for循环结束。

我们进行编译

strerror

 参数是一个整型,返回值是一个char类型的指针。

函数的作用:返回错误码对应的错误信息。

c语言的库函数在执行失败的时候,都会返回失败的时候,都会返回错误码。

每个错误码对应不同的错误信息。

返回值是错误码对应的错误信息的字符串的首字符的地址。

先举一个例子

#include<string.h>
#include<stdio.h>
int main()
{
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	return 0;
}

我们进行编译

 这就是错误码对应的错误信息。

举一个例子

#include<errno.h>
int main()
{
	FILE*pf=fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s", strerror(errno));
	}
	return 0;
}

其中,errno是C语言设置的一个全局的错误码存放的变量。

fopen是打开文件夹的意思,第一个参数是文件夹的名字,第二个参数r的意思是读,这里的意思是以读的形式打开文件夹test.txt,然后把文件夹的地址传递给pf指针,假如pf是空指针,就证明产生了错误,这时候我们在if语句中打印错误信息。

我们进行编译:

 没有文件或者没有文件夹,证明我们并没有文件test.txt。

字符分类函数:

 这么多函数,我们先实验一下iscntrl函数

为什么参数是整型?

答:因为字符的ASCII码值是整数。

这个函数的意思是:如果c是一个空白字符,那么我们返回一个非0的数字,如果c不是一个空白字符,那么我们返回0.

我们测试一下。

#include<ctype.h>
int main()
{
	int a = iscntrl('w');
	printf("%d", a);
}

这个函数的头文件是#include<ctype.h>

字符w并不是空白字符,我们的函数iscntrl的返回值应该为0,我们进行编译。

 结果正确。

我们换一个函数isdigit,进行实验。

int main()
{
	int a = isdigit('0');
	printf("%d", a);
}

我们进行编译

 结果不为0.

isdigit判断是不是数字字符(这些字符是1,2,3,4,5,6,7,8,9)

两个字符转换函数

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

这两个函数是大小写转化函数,第一个函数能把大写字母转化为小写字母,第二个函数能把小写字母转化为大写字母。

我们举一个例子

int main()
{
	int a = tolower('W');
	printf("%c", a);
	return 0;
	
}

我们进行编译吗,可以发现,大写字母转化为小写字母

 注意:对于 int tolower(int c)函数,假如参数为非大写字母的任意其他字符,字符不会发生任何改变。int toupper(int c)也是同理。

memcpy:内存拷贝。

strcpy和strncpy都是字符串拷贝,不能够拷贝其他类型,例如

int arr1[] = { 1, 2, 3, 4, 5 };
	int arr2[30] = { 0 };

假如我们要把数组arr1内部的元素拷贝到数组arr2,上述两个函数就不起作用了。

memcpy的结构

函数的参数有三个:第一个是指向目的地空间的无类型指针,第二个是指向来源空间的无类型指针,第三个是无符号整型,返回值是一个无类型指针。

我们进行实验  

int main()
{
	int arr1[] = { 1, 2, 3, 4, 5 ,6,7};
	int arr2[30] = { 0 };
	memcpy(arr2, arr1, 28);
	return 0;
}

进行调试

 我们可以发现,数组arr1内部的元素全部被拷贝到数组arr2中了。

总结memcpy的用法:将第二个参数指向的数组 的元素拷贝到第一个参数指向的数组,第三个参数代表拷贝的数量(单位是字节)。

这时候,我们再回看这个函数的类型可以发现

这个函数参数的两个指针为什么是void*?

答:因为void*的指针能够接受任意类型的参数,无论是int*还是char*还是float*。 

我们进行模拟实现memcpy。

#include<string.h>
#include<assert.h>
void*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;
}
int main()
{
	int arr1[] = { 1, 2, 3, 4, 5 ,6,7};
	int arr2[30] = { 0 };
	memcpy(arr2, arr1, 28);
	return 0;
}

memcpy函数的返回值是一个void型的指针,指向的元素是数组arr1首元素的地址。

memcpy负责拷贝两个独立空间中的数据。,不能拷贝两个相同空间的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值