【C语言加油站】字符函数与字符串函数

字符函数与字符串函数

  • 导言
  • 一、字符分类函数
    • 1.1 字符分类函数的用法
  • 二、字符转换函数
    • 2.1 字符转换函数的用法
  • 三、字符串函数
    • 3.1 成员
    • 3.2 strlen函数
      • 3.2.1 size_t类型
      • 3.2.2 strlen的易错点
      • 3.2.2 strlen的使用
      • 3.2.3 strlen与sizeof
    • 3.3 strcpy函数和strncpy函数
      • 3.3.1 strcpy和strncpy的使用
      • 3.3.2 小结
    • 3.4 strcat函数和strncat函数
      • 3.4.1 strcat和strncat的使用
      • 3.4.2 小结
    • 3.5 `strcmp`函数和`strncmp`函数
      • 3.5.1 strcmp和strncmp的使用
      • 3.5.2 小结
    • 3.6 查找子字符串——`strstr`
      • 3.6.1 字符串的基本概念
      • 3.6.2 strstr
      • 3.6.3 strstr的使用
      • 3.6.4 strstr的底层逻辑
    • 3.7 拆分字符串为标记——`strtok`
      • 3.7.1 strtok的使用
      • 3.7.2 小结
    • 3.8 获取错误信息字符串——`strerror`
      • 3.8.1 strerror
      • 3.8.2 errno
      • 3.8.3 perror
      • 3.8.4 strerror、errno和perror的使用
  • 结语

封面

导言

大家好,很高兴又和大家见面了!!!
从咱们学习C语言的开始,我们就接触了一个数据类型——字符类型。并且在之后的学习过程中,我们经常与这一类型的元素打交道,如字符变量、字符数组、字符指针……

与这些类型密切相关的就是字符与字符串,我们经常要对这些字符和字符串进行一些操作,如字符小写转大写、判断是不是小写字符、计算字符串长度……

为了方便程序猿来处理这些字符和字符串,C语言为程序猿提供了一系列的库函数,这就是我们今天要介绍的字符函数与字符串函数;

一、字符分类函数

字符,可以简单的理解为只要是键盘上能敲出来的都是字符,前面我们有介绍一个内容——ASCII码表。

ASCII码表
从表中我们可以看到这里面的字符有各式各样的,这些字符分为两大类——控制字符与打印字符。而打印字符又分为数字字符、标点符号、小写字符、大写字符……对于这些字符,C语言提供了一类专门用于进行字符分类的函数,如下所示:

 iscntrl——判断是不是控制字符
 isspace——判断是否为空白字符
 isdigit——判断是否是数字字符
 isxdigit——判断是否是十六进制数字字符
 islower——判断是否是小写字母
 isupper——判断是否是大写字母
 isapha——判断是否是字母
 isalnum——判断是否是字母或者数字
 ispunct——判断是否是标点符号
 isgraph——判断是否是图形字符
 isprint——判断是否是可打印字符,包括图形字符和空白字符

这些函数都是收录在头文件<ctype.h>中,所以我们在使用这些函数时,需要引用这个头文件。

1.1 字符分类函数的用法

这些函数的用法十分相似,使用的基本逻辑就是在传入想要分类的字符后,通过函数的返回值来判断是否为对应的函数类型:

  • 符合条件返回非零值;
  • 不符合条件返回0;

这里我们以islower函数为例来说明这类函数的用法。我们先打开MSDN来看看这个函数:

islower函数
islower函数的原型如下所示:

int islower(int c);

其他的字符分类函数的原型和islower函数一样,都是返回类型为整型,参数为整型的库函数。之所以参数为整型,是因为这些函数是根据这些字符的ASCII码值来进行分类判断的,如小写字符的ASCII码值是在97-122,如果我们不使用islower,我们就可以如下进行编码:

int main()
{
	char a = 0;
	scanf("%c", &a);
	if (a >= 97 && a <= 122)
		printf("%c的ASCII码值为%d,是小写字母\n", a, a);
	else
		printf("%c的ASCII码值为%d,不是小写字母\n", a, a);
	return 0;
}

如果我们使用islower函数,我们则可以写成:

int main()
{
	char a = 0;
	scanf("%c", &a);
	if(islower(a))
		printf("%c的ASCII码值为%d,是小写字母\n", a, a);
	else
		printf("%c的ASCII码值为%d,不是小写字母\n", a, a);
	return 0;
}

下面我们来运行一下这个代码:
islower函数2
其他的字符分类函数的用法和islower如出一辙,大家只要在使用时别忘记头文件<ctype.h>就行了。

二、字符转换函数

与字符分类函数相同,C语言为程序猿提供了两个用来进行字母大小写转换的函数——tolowertoupper。我们同样通过MSDN来认识一下这两个函数:

tolower、toupper函数
从这里的介绍可以看到,tolowertoupper这两个函数与前面的字符分类函数一致,都是一个返回类型为整型,参数类型为整型的库函数,它们的作用就是进行字符的大小写转换。下面我们就来看一下它们的用法;

2.1 字符转换函数的用法

我们通过ASCII码表可以知道,大写字母与其对应的小写字母的ASCII码值相差32,也就是说,如果我们不借用这个字符转换函数的话,我们可以通过字母±32来达到转换大小写的目的,如下所示:

int main()
{
	char a = 0;
	scanf("%c", &a);
	printf("转换前字符为%c,对应的ASCII码值为%d\n", a, a);
	if (a >= 'A' && a <= 'Z')
		a += 32;
	else if (a >= 'a' && a <= 'z')
		a -= 32;
	printf("转换后字符为%c,对应的ASCII码值为%d\n", a, a);
	return 0;
}

如果我们通过函数来进行转换,我们就可以编写:

int main()
{
	char a = 0;
	scanf("%c", &a);
	int b = 0;//接收转换后的值
	if (islower(a))
	{
		b = toupper(a);
	}
	else if (isupper)
	{
		b = tolower(a);
	}
	printf("转换前字符为%c,对应的ASCII码值为%d\n", a, a);
	printf("转换后字符为%c,对应的ASCII码值为%d\n", b, b);
	return 0;
}

根据MSDN的描述,此时转换后的值是字符a的一个副本,也就是说,字符转换函数并未改变a的值,下面我们就来测试一下:

tolower与toupper函数2
从测试结果中我们可以得到以下信息:

  1. 字符转换函数不是直接对操作对象进行转换,而是额外生成了一个副本
  2. 当操作对象不符合条件时,不会对其进行任何操作

字符分类函数与字符转换函数都是比较简单的函数,相信大家现在都能理解并会使用这两个函数了,这里我还是要提醒大家一下几点:

  • 别忘记引用对应的头文件<stdlib.h><ctype.h>
  • 字符转换函数的返回值别忘记使用变量来接收,否则会报错。

三、字符串函数

字符串我们已经介绍过多次了,它的定义是由双引号引起的一个或多个字符就叫做字符串。我们在前面的学习中,对字符串掌握了以下知识点:

  1. '\0'是字符串的结束标志;
  2. 字符串自带一个'\0'
  3. ""这个是空字符串,字符串里的元素只有一个'\0'

对于字符串,我们也需要对它进行一些操作,如计算字符串长度、两个字符串之间比较大小……C语言为了提高程序猿的编程效率,它为程序猿提供了一系列用来对字符串进行操作的函数,简称字符串函数。这些函数都位于头文件<string.h>,我们在使用这些字符串函数时,需要引用这个头文件;

3.1 成员

下面我们通过网站cplusplus.com来看一下在<string.h>这个头文件中有哪些库函数:

字符串函数
今天我们将介绍这其中的部分字符串函数,按它们各自的功能分类,有以下几类:

  1. 求字符串长度——strlen
  2. 字符串拷贝——strcpystrncpy
  3. 追加字符串——strcatstrncat
  4. 字符串比较——strcmpstrncmp
  5. 查找子字符串——strstr
  6. 拆分字符串为标记——strtok
  7. 获取错误信息字符串——strerror

下面我们将一一介绍这些字符串函数;

3.2 strlen函数

strlen的全称是string length——字符串长度,这个函数是专门用来求取字符串长度的库函数。

为了更详细的介绍strlen,这里我借助MSDN来获取strlen函数的相关信息:

strlen函数

在使用strlen函数时,通过这个介绍,我们可以得到以下几点信息:

  1. strlen函数是通过读取\0进而计算\0前的字符个数(不包含'\0');
  2. strlen的返回值是size_t类型的值;

这里可能就会有朋友有疑问了,size_t是一个什么类型,下面我们一起来探讨一下;

3.2.1 size_t类型

size_t类型
这里是通过C++图书馆的网站上找到的解释,可以看到,它是一种无符号整型,也就是说,这种类型的整数的二进制没有符号位,下面我们来测试一下:

size_t类型2
有没有发现,同样是3-6,但是得出来的结果却截然不同,下面我们来通过二进制序列分析一下:

//-3的二进制序列
1000 0000 0000 0000 0000 0000 0000 0011——原码
1111 1111 1111 1111 1111 1111 1111 1100——反码
1111 1111 1111 1111 1111 1111 1111 1101——补码

前面我们有介绍过,整型在内存中都是通过补码进行存储的,此时计算机拿到的是-3的补码:

  • 对于有符号整型来说,计算机在拿到-3的补码之后,会将其转化为原码输出;
  • 但是对于无符号整型来说,原码=反码=补码,所以,计算机直接将其进行输出;

将其转化为十进制的话就能得到式子: S 32 = 2 0 + 2 1 + 2 2 + … … + 2 29 + 2 30 + 2 31 − 2 1 S_{32}=2^0+2^1+2^2+……+2^{29}+2^{30}+2^{31}-2^1 S32=20+21+22+……+229+230+23121
根据等比数列的求和公式: S n = a 1 ∗ ( 1 − q n ) / ( 1 − q ) S_n=a_1*(1-q^n)/(1-q) Sn=a1(1qn)/(1q)
我们很容易的得到式子: S 32 − 2 1 = a 1 ∗ ( 1 − q 32 ) / ( 1 − q ) − 2 1 = 1 ∗ ( 1 − 2 32 ) / ( 1 − 2 ) − 2 = 2 32 − 3 S_{32}-2^1=a_1*(1-q^{32})/(1-q)-2^1=1*(1-2^{32})/(1-2)-2=2^{32}-3 S3221=a1(1q32)/(1q)21=1(1232)/(12)2=2323

size_t类型3
最终我们就得到了测试结果。相信大家经过这个探讨,应该都能理解什么事无符号整型了。下面我们继续介绍strlen

3.2.2 strlen的易错点

因为strlen的返回值是一个无符号整型,很多朋友可能都会忘记这个点,所以容易导致一些错误,如下所示:

//strlen的易错点
int main()
{
	if (strlen("abc") - strlen("abcd") < 0)
		printf("小于\n");
	else
		printf("大于\n");
	return 0;
}

字符串"abc"的长度为3,字符串"abcd"的长度为4,将这两个长度作差,我们可以得到的是什么结果呢?

strlen的易错点
可以看到,此时得到的是大于,这就是因为strlen的返回值为无符号整型,正常我们进行整型运算3-4得到的是-1,但是在无符号整型进行运算的时候会得到一个很大的数字。

strlen函数我们在之前有过简单的介绍它的使用,今天我们来详细介绍一下;

3.2.2 strlen的使用

strlen的使用比较简单,它的参数是一个字符指针,既然是指针,我们对其传参时可以是字符数组的数组名、也可以是字符串,还可以是存储常量字符串的字符指针,它会计算字符串中字符的数量,但是不包括'\0',如下所示
strlen的用法
下面我们来使用一下strlen计算字符串、字符指针和字符数组:

strlen的使用2
有一点我们一定要注意:

  • 参数指向的字符串必须以'\0'结束,否则strlen将会返回一个随机值

如下所示:

strlen的使用3
可以看到,在数组ch1中并没有'\0',此时strlen会对数组进行越界访问,直到找到一个'\0'才会停止。

现在大家应该都直到如何使用strlen了,不过有一个操作符——sizeof——计算操作数所占内存空间大小,可能有朋友容易将他俩搞混,下面我们来介绍一下它们之间的区别;

3.2.3 strlen与sizeof

现在我们来测试一下strlensizeof

strlen与sizeof

我们通过表格的形式来更加直观的介绍它们之间的区别:

区别sizeofstrlen
性质不同sizeof是一个操作符,不需要引用头文件strlen是一个库函数需要引用头文件<string.h>
工作原理不同sizeof是计算操作数所占内存空间大小,单位是字节strlen是计算字符串中’\0’之前的字符个数,单位是个
操作对象不同sizeof不关注计算的是什么对象strlen的操作对象必须是字符类型,并且还需要关注操作对象是否有’\0’

现在strlen我们就已经介绍完了,下面我们继续看下一类函数——字符串拷贝函数——strcpystrncpy

3.3 strcpy函数和strncpy函数

这个两个函数都是用来进行字符串拷贝的,我们先来看一下这两个函数的介绍:
strcpy和strncpy
从介绍中我们可以看到,strcpystrncpy这两个函数都是将第二个参数的字符串内容拷贝到第一个参数中,但是strncpy相比于strcpy多了一个参数——拷贝字符串的个数,也就是说,strcpy它是直接将整个字符串拷贝过来,但是strncpy会根据具体的个数进行拷贝;

我们继续来看一下这两个函数是怎么使用的;

3.3.1 strcpy和strncpy的使用

strcpy和strncpy的使用
对于这两个函数的使用,我们可以简单的理解为;

  • strcpy是将整个字符串包括’\0’拷贝到目标字符串的指定位置,所以源对象需要有’\0’;
  • strncpy是将指定数量的字符拷贝到目标字符串中,源对象不一定要有’\0’;
  • 不管是strcpy还是strncpy它们的操作对象都不能有重叠;

接下来我们通过不同的测试来进一步介绍它们的用法;

  1. 原目标有无'\0'strcpystrncpy的影响

下面我们来对这两个函数进行第一次测试,源目标有’\0’和没有’\0’的区别:

strcpy和strncpy的用法2
这次测试结果很好的说明了一个问题,strcpy函数和strlen函数一样,也是通过寻找’\0’从而停止函数的继续运行,而strncpy只关心需要拷贝的字符数量,并不关心源对象是否有'\0'

  1. strncpy的拷贝数量与源对象的字符数量不相等

下面我们测试一下strncpy在拷贝的对象数量不等于源对象字符数量时会怎么处理:

strcpy和strncpy的用法3
从这次的测试结果来看,strncpy在拷贝小于源对象字符个数的字符时是不关心'\0'的,但是在拷贝大于源对象字符个数的字符时,就有区别了:

  • 源对象有'\0',则多出的字符个数用'\0'继续填补;
  • 源对象没有'\0',则多出的字符个数用'?'继续填补;
  1. strcpystrncpy对指定的位置进行拷贝

接下来我们来测试一下对strcpystrncpy指定的位置进行拷贝:

strcpy和strncpy的用法4
这次测试结果证明了strcpystrncpy这两个函数都是可以指定位置进行拷贝的,既可以指定拷贝源对象的起始点,也可以指定拷贝目标对象的起始点;

  1. 拷贝的目标对象空间小于源对象

接下来我们测试一下如果目标对象的空间小于源对象的空间时,函数又该如何处理:
strcpy和strncpy的用法5
可以看到此时如果当拷贝的字符数量超过目标空间的大小时,就会造成目标空间的堆栈损坏,所以目标空间需要足够大,至少能放入需要拷贝的所有字符才行;

  1. 字符指针之间的字符串拷贝

接下来我们来测试一下如果对象为字符指针时,strcpystrncpy又是如何处理的:

strcpy和strncpy的用法6
可以看到当源对象为指针时,是不影响函数进行拷贝的,但是当目标为指针时,此时指针如果被赋予了不可修改的值,如这里的空指针和常量字符串,此时函数也是无法进行拷贝的;

  1. 将源对象的字符拷贝给源对象

接下来我们来测试一下能不能实现自我拷贝:
strcpy和strncpy的用法7
这次测试我们可以看到,两个函数都是能够进行自我拷贝的,但是,我们会发现它们结果与我们所想的结果有点不太一样,会出现这种情况也是因为这两个函数对于拷贝空间有重叠的情况是标准未定义的,虽然此时是能正常输出,但是结果却是错误的,所以最好不要使用这两个函数来进行自我拷贝;

3.3.2 小结

经过前面的介绍,对于strcpy和strncpy这两个函数,我们可以做一个小结:

  1. strcpy是以字符串终止标志’\0’作为拷贝的结点,将指定起点的整个字符串拷贝到目标对象中
  2. strncpy是以给定的拷贝字符的个数为标准进行拷贝,此时会出现以下情况
    • 给定的个数小于或等于源对象字符个数,此时函数正常进行拷贝
    • 给定的个数大于源对象字符个数,此时源对象有’\0’则多出的字符通过’\0’填补
    • 给定的个数大于源对象字符个数,此时源对象没有’\0’则多出的字符通过’?'填补
  3. 源对象需要有’\0’
  4. 目标对象的空间需要足够大,至少能放入拷贝的字符个数
  5. 目标对象需要能够被修改
  6. 源对象与目标对象的空间不能够重叠

3.4 strcat函数和strncat函数

现在我们介绍的这两个函数是用来进行字符串追加的,它们与字符串拷贝不同,但又有相似之处。下面我们来看一下这两个函数的介绍:

strcat和strncat
可以看到strcatstrcpy的参数一样,strncatstrncpy的参数是一样的,也就是说,它们的用法也很相似,只不过是功能不同而已:

  • strcpystrncpy是将源对象的字符拷贝到目标对象中;
  • strcatstrncat是将源对象的字符添加到目标对象中;

这时有朋友可能就会好奇了,一个拷贝,一个增加,到底有什么区别呢?下面我们就来看看strcatstrncat的用法;

3.4.1 strcat和strncat的使用

strcat和strncat的使用
从函数的介绍中,我们可以得到几点信息:

  • 源对象和目标对象都需要有’\0’,追加后的字符串也会使用’\0’作为结束标志;
  • 追加的实现是将源对象的第一个字符覆盖目标对象的’\0’来实现追加;
  • 两个字符串的空间不能够重叠;
  • strcat是将源对象添加到目标对象中;
  • strncat是将指定的字符个数添加到目标对象中;

下面我们来测试一下这两个函数的用法;

  1. '\0'对函数的影响

NULL对函数的影响

在测试'\0'对函数的影响时我们为了保证目标对象有足够的空间来接收源对象的内容,因此我们无法测试目标对象中没有'\0'的情况。

从测试结果中我们可以看到,当源对象和目标对象中都存在'\0'时,此时两个函数都是能够正常使用的,但是当源对象没有'\0'时,strcat函数则无法正常使用;而strncat函数在进行追加时,因为是根据我们传入的字符个数来进行追加的,所以当我们追加的字符个数与源对象的长度一致时并不影响函数的正常运行;

  1. 空间重叠对函数的影响

空间重叠对函数的影响
从测试结果中可以看到,对于指定追加个数的strncat函数来说,在空间有重叠的情况下函数依旧能够正确的完成追加,但是对于strcat函数而言,由于它在追加时是需要将源对象的'\0'一并追加到目标对象中,当函数进行自我追加时,原先拥有的'\0'被追加的内容给覆盖掉了,导致函数始终无法找到源目标的'\0',最后得到的结果就是一直进行追加。

  1. 追加的数量对strncat函数的影响

追加数量对函数的影响
在这次的测试中,我们通过追加0~4个字符数量对函数strncat进行了探讨,从监视窗口中不难看出:

  • 当数量为0时函数不会执行任何操作;
  • 当数量大于0小于或等于源对象的长度时,函数会在目标对象的末尾新增一个'\0'
  • 当数量大于源对象的长度时,函数会按照源对象的长度对目标对象进行追加,并在追加完字符后新增一个'\0'
  1. 不同起点对函数的影响

不同期待你对函数的影响
这一次我们分别测试了改变目标对象的起点与源对象的起点。从测试结果中我们可以看到当我们在移动目标对象的起点后,函数返回的也是移动后的目标对象;当我们移动了源对象的起点后,目标对象中追加的内容也是源对象移动后的内容。

3.4.2 小结

经过上面的探讨,我们可以得到以下结论:

  1. strcatstrncat是用于将源对象追加到目标对象末尾的库函数;
  2. strcatstrncat在进行追加时是通过使用源对象的第一个字符覆盖目标对象的第一个'\0'来进行追加的
  3. strcat在进行追加时受源对象中的'\0'的影响,当追加的对象空间重叠时可能会导致字符串中的'\0'被覆盖而进入追加的死循环;
  4. strncat在进行追加时受指定的字符数量的影响:
    • 当追加字符数量为0时函数不进行任何操作;
    • 当追加字符数量大于0小于或等于源对象的长度是,函数正常追加并在目标对象的末尾新增一个'\0'
    • 当追加字符数量大于源对象的长度时,函数会按源对象的长度进行追加,并在目标对象末尾新增一个'\0'
  5. 函数在进行追加操作时会根据传参时的对象进行操作:
    • 当源对象的地址发生改变时,会提取改变后的源对象中的内容;
    • 当目标对象的地址发生改变时,追加操作会根据改变后的起始点开始寻找第一个'\0'进行追加并在完成追加后返回传入的目标对象的地址;

3.5 strcmp函数和strncmp函数

对于两个字符串来说,它们也是能够进行大小比较的,比较的依据并不是根据字符串的长度,而是根据字符串中同位序字符的ASCII码值进行比较的。下面我们就来看一下C语言给我们提供的两个用于进行字符串大小比较的函数strcmpstrncmp
strcmp和strncmp
从函数的介绍中我们可以这两个函数的功能是一样的,但是还是有些许区别:

  • strcmp比较的就是两个字符串
  • strncmp比较的是两个字符串中的字符,也就是两个字符串的子串

3.5.1 strcmp和strncmp的使用

为了弄清它们的用法,我们继续往下看;
strcmp和strncmp的使用
这里的用法介绍可能有点不好理解,我给大家解释一下:

  • strcmp是按照字母表的顺序将两个字符串中的字符进行挨个比较,并根据比较的结果返回对应的值;
  • strncmp是按照字母表的顺序将两个字符串中的子串进行挨个比较,并根据比较的结果返回对应的值;

大家需要注意的是介绍中说的字母表的顺序并不真的就是英文的字母表,这里的字母表更多的是指计算机中的ASCII码表。这里我们通过对同一个字母a的大小写进行测试,来说明比较函数比较时的底层逻辑:
底层逻辑
对于字符'a''A'来说小写字符的ASCII码值是要比大写字母大32的,但由这两个字符组成的两个字符串进行比较时就会有下面三种情况:

  • 用字符串"a"与字符串"A"进行比较,对应的ASCII码值的比较结果为 97 > 65 97>65 97>65 也就是字符串1大于字符串2,我们可以看到函数返回的是1;
  • 用字符串"a"与字符串"a"进行比较,对应的ASCII码值的比较结果为 97 = 97 97=97 97=97 也就是字符串1等于字符串2,我们可以看到函数返回的是0;
  • 用字符串"A"与字符串"a"进行比较,对应的ASCII码值的比较结果为 65 < 97 65<97 65<97 也就是字符串1小于字符串2,我们可以看到函数返回的是-1;

相信大家看到这里应该就能明白了,其实不管是strcmp还是strncmp,它们在进行比较时就是从给定的字符串的起点开始挨个往后进行比较,如果比较过程中所有的字符都相等,那么返回的结果就是0,当出现不相等时,就会根据比较的结果返回对应的值。两个函数的区别就在于后者是根据给定的字符个数进行比较,如下所示:

函数的区别

可以看到,对于同样的字符串,只因为我们通过strncmp时比较的是4个字符,最终得到的是两个结果,这也进一步说明了几个问题:

  1. 函数在进行比较时,strcmp是比较整个字符串,strncmp是比较指定的字符数量;
  2. 字符串比较的底层逻辑是对比两个字符串中同位序的字符的ASCII码值的大小;
  3. 函数的返回值是根据第一次出现的不同字符的比较结果进行返回,当两个字符串的字符完全相同才返回0;

对于strcmpstrncmp这两个函数来说,还是有些地方需要我们仔细的探讨一下的。

  1. 有无'\0'对函数的影响

对于这两个函数,我们从介绍中并未看到函数在使用时提及字符串终止符,因此'\0'的有无对函数的运行有无影响就是需要我们关注的第一个问题:
函数的使用1
这里我们测试了3中情况,由测试结果可以看到:

  • 对于strcmp来说,它在执行字符串比较时关注的是给定的字符个数,因此有无'\0'对它来说并不重要;
  • 对于strcmp来说,它比较的是整个字符串,当字符串中没有’\0’时,如果已存在的字符都是相等的,这时函数并未找到字符串终止符,函数会继续往后查找,这时查找的结果就变成不可预测的了。就像test4函数中,函数查找到第5个字符时,ch1的字符为'\0',对应的ASCII码值为0,而字符串ch2中的字符是未知的,从结果上看,它对应的ASCII码值肯定是大于0的;在test5函数中两个字符串的已知字符都相等,因此函数会继续查找第5个字符,这时两个字符串中的第5个字符都是未知的,从结果上可以看到此时ch2后面的字符对应的ASCII码值是大于ch1后面的字符对应的ASCII码值的;
  1. '\0’的位置对函数的影响

由上一个测试可知,'\0'的有无对函数strcmp来说是有很大的影响的,现在我比较好奇的是如果在比较的字符串中都有'\0'但是'\0'的位置不一定在字符串末尾,这时对两个函数又会有什么影响呢?

函数的使用2
下面我们来分析一下这次的三个测试用例:

  • test6中,虽然字符数组中'\0'后的内容并不相同,但是得到的比较结果两个函数都为0,也就是说两个函数都是比较到'\0'就停止了。从这次测试中我们可以得到一个结论——两个函数在比较字符时都是以'\0'作为结束标志;
  • test7test8中,当'\0'位于不同位置时,'\0'的位序靠前的对象会小一点。这个测试结果其实不难理解,我们知道这两个函数都是一个字符一个字符的进行比较,当比较到第三个字符时,ch1的字符为'\0'对应的ASCII码值为0,ch2的字符只要不是'\0'那它的ASCII码值肯定大于0,因此函数就不会继续往后比较了。从这两次测试中我们可以得到一个结论——当两个长度不相等的字符串进行比较时,字符串中第一个'\0'之前的相同位序上的元素都相等,那么长字符串大于短字符串;

3.5.2 小结

从上面的介绍中,我们可以对这两个函数总结以下结论:

  1. strcmpstrncmp在进行字符串比较时的底层逻辑是对相同位序上的字符的ASCII码值进行比较;
  2. 函数在对两个字符串进行比较时,会以第一个不相等的字符的比较结果作为两个字符串的比较结果:
    • ASCII码值大的字符所在字符串大于ASCII码值小的字符所在字符串;
    • 当两个字符串中所有的字符都相等时,这两个字符串才相等;
  3. strcmpstrncmp在进行比较时都是以'\0'作为结束标志;
  4. 在已有元素相同且顺序相同的情况下,当两个字符数组中'\0'的位置不相等时,'\0'的位序靠前的字符数组小于'\0'的位序靠后的字符数组;
  5. 在两个元素相同但长度不同的字符串中,长字符串大于短字符串;

3.6 查找子字符串——strstr

3.6.1 字符串的基本概念

在介绍这个strstr函数之前我们需要先了解几个字符串的基本概念:

  • 主串:源字符串
  • 子串:源字符串中能够找到的任意多个连续的字符组成的子序列
  • 字符在串中的位置:字符在串中的序号
  • 子串在主串中的位置:子串的第一个字符在主串中出现的位置

为了方便大家更好的理解,这里我们以字符串"hello"为例,来说明这些概念:

字符串"hello"它就是一个主串;
字符串"hell""ell""lo""o"""等能够在主串中找到的这些任意多个连续字符组成的子序列都是它的子串;(PS:这里的多个是个泛指,不要较真哦!!!)
在主串中的这些字符'h''e''l''l''o'所对应的序号为1、2、3、4、5,这些序号也就是字符在串中的位置;(PS:字符串中的序号是从1开始,字符串中字符的下标是从0开始,二者相差1)
对于子串"ell"来说,它第一次出现在主串中的位置就是字符'e'在串中的位置,也就是2。

在很多时候,我们都会遇到需要我们在某个字符串中查找子串的位置,这种定位子串的操作我们将其称为字符串的匹配模式

3.6.2 strstr

C语言在头文件<string.h>中提供了一个专门用来定位子串的库函数——strstr。这是一个非常重要的库函数,下面我们来认识一下这个函数:
strstr
从函数的原型中我们可以看到strstr这个函数有两个参数,第一个参数主串的类型为char*,第二个参数子串的类型为const char*类型,函数的返回值也是一个char*的指针;

从函数返回值的介绍中我们可以看到strstr这个函数返回的是子串在主串中第一次出现的地址,而字符串的地址就是字符串第一个字符的地址;

3.6.3 strstr的使用

了解了函数的基本信息后,下面我们就可以来测试一下函数的用法了,如下所示:

strstr的使用
这里大家一定要注意字符在字符串中的下标和字符在字符串中的序号的区别。从这里的用法演示中我们可以看到strstr这个函数的使用并不困难,关于函数的用法我就不再多加赘述了,下面我们来了解一下该函数的底层逻辑。

3.6.4 strstr的底层逻辑

strstr函数在运行时,实际上就是通过将子串中的字符与主串中的字符一个一个的进行比较,当子串中的字符全部都能在主串中找到时,那就说明主串中存在该子串,此时函数就会返回主串中子串的首字符的地址,如下所示:
字符串匹配演示
通过这个动图相信大家也能更好的理解strstr函数的一个工作原理。当然这里展示的是字符串的朴素匹配模式,strstr函数实际在运行时的效率会更高。

字符串匹配模式算法包含朴素匹配模式算法和KMP匹配模式算法,相关的知识点我会在【数据结构】专栏中详细介绍,这里我就不再展开介绍了,大家如果对该内容感兴趣的话可以关注该专栏。

3.7 拆分字符串为标记——strtok

在日常生活中,我们可能会遇到将一条信息拆分成多条信息的情况,就比如我现在需要大家将出生年月日xxxx-xx-xx分别提取出来,大家此时会怎么做呢?

有朋友会说,这还不简单,我直接化身成CV工程师,一下就解决了。这个也确实不失为一种方法,但是如果此时是需要提取100个人的信息、1000个人的信息、10000个人的信息……这时CV工程师还可行吗?

很显然在这种数据量庞大的情况下CV工程师并不是一个好的解决方式。为了更加高效的完成提取工作,C语言在头文件<string.h>中给我们提供了一个用来拆分字符串的函数strtok。下面我们就来一起了解一下这个库函数:
strtok
从函数的介绍中我们可以看到这个函数是用来找到字符串中的下一个标记的,至于这个标记是什么,我们还不清楚;
在函数的原型中可以看到这个函数有两个参数一个是char*类型的参数,一个是const char*类型的参数,函数的返回类型也是char*
在函数的返回值介绍中我们可以看到,这些函数是用来在strToken中找到标记并返回指向该标记的指针,如果找不到标记就返回NULL;当找到标记时就会用NULL来替换每个分隔符;

看到这个介绍我们还是比较懵,这里又是提到了标记又是提到了分隔符,这些都是什么意思呢?别着急我们继续往下看;

3.7.1 strtok的使用

为了弄清楚什么是标记什么是分割符,我们需要来看一下这个函数具体时如何使用的:
strtok的使用

从Remarks介绍中我们可以得到以下信息:

  • 函数所提到的标记是位于strToken这个字符串中的;
  • 在隔符字符串strDelimit中的这些分割符也能够在strToken中找到

因此我们可以大胆推测strToken这个字符串的结构应该是由"标记""分割符"组成。

现在我们再来看一下年月日的字符串结构"xxxx-xx-xx",在这个字符串中,将年、月、日给隔开的字符为'-'。按照strtok函数的介绍来看,在"xxxx-xx-xx"这个字符串中,年、月、日这些信息就是函数提到的标记,而将它们隔开的字符'-'就是分割符。

在下面的函数使用介绍中我们可以得到以下信息:

  • 第一次调用strtok时,函数会跳过strToken中的前导分割符并返回第一个标记的地址,并修改strToken这个字符串;
  • 当我们想获取后面的标记时,我们需要将strToken这个参数的值改为空指针;
  • 分隔符字符集Delimit在不同的调用中可以接收不同的分隔符,以便字符串中的分隔符发生变化;

为了更好的理解strtok这个函数的用法,下面我们可以做一个测试:
strtok函数测试

从这次测试中我们可以得到以下结论:

  1. strtok函数在每次调用时能且只能找到一个标记并返回;
  2. strtok函数在第一次调用时会改变第一次调用传入的字符串;
  3. 在后续的调用中如果传入字符串为NULL,则可以继续查找被修改过的字符串的后续标记;
  4. 当被修改过的字符串中已经没有标记时,则返回空指针;
  5. 当传入的字符串中无法找到分隔符集中的分隔符时,返回该字符串;

这里需要注意的是传入的字符串应该是可被修改的,如果传入的是常量字符串,则函数无法正常运行,如下所示:
strtok函数测试2
可以看到当我们将ch1的类型由字符数组类型改为字符指针类型后,此时的ch1就变成了一个内容不可修改的常量字符串,这时我们再来调用strtok函数时,因为函数会对ch1中的内容进行修改,所以就出现了写入冲突的错误。

在函数的使用中有提到分隔符字符集中的内容在不同的调用中也是可以进行修改的,那具体能不能修改呢?我们来测试一下:

strtok测试3
从这次的测试中我们可以得到以下结论:

  1. strtok在第一次调用时会跳过前导分隔符;
  2. 在后续的调用中分隔符字符集中的内容是可以被修改的;

3.7.2 小结

经过前面对strtok函数的介绍,关于strtok函数的用法,我们可以总结为以下几点:

  1. strtok函数的参数分别是待分割的字符串strToken和字符串中的分隔符集Delimit
  2. strToken字符串必须是能够被修改的字符串;
  3. Delimit字符集中的分隔符可以被修改;
  4. strtok在第一次调用时会跳过strToken中的前导分隔符;
  5. strtok函数在调用时,函数会对不同的情况做出不同的处理:
    • 如果字符串strToken中存在标记和分隔符,会将标记末尾的分隔符修改为'\0'并返回一个指向该标记的指针;
    • 如果字符串strToken中不存在分隔符,则会返回指向字符串strToken的指针;
    • 如果字符串strToken中不存在标记,则会返回NULL;
  6. strtok每次调用只能查找一个标记;
  7. 在第一次调用中,如果字符串strToken中存在分割符,则函数会修改字符串;
  8. 在后续的调用中,如果想要继续查找被修改的字符串strToken中后续的标记,需要将参数strToken改为NULL;

3.8 获取错误信息字符串——strerror

3.8.1 strerror

下面我们要介绍的strerror这个库函数它并不是用来对字符串进行操作的库函数,它的作用是当系统出现错误时,获取系统错误信息的库函数。下面我们借助cplusplus网站来认识一下这个库函数:
strerror
网站中对这个库函数解释的很详细了,这个函数的具体作用我们简单的理解就是:

  • 当程序发生错误时,会产生一个错误号码,当我们将对应的错误号码传给函数时,函数会生成一个不可修改的字符串,这个字符串就是用来解释这个错误号码的含义的。

3.8.2 errno

在上面的介绍中还提到了一个由库函数设置的errno,这个errno是什么呢?我们继续借助网站来认识一下;
errno
errno的介绍中我们可以得到以下信息:

  • 它是被定义的一个宏常量;
  • 这个宏常量在程序启动时会被设置为0;
  • 这个宏常量的值可以被C标准库中的任意库函数修改为不同于0的值;
  • 该宏常量位于头文件<errno.h>中;
  • errno不同的值对应的是不同的错误,我们可以通过strerror来获取对应的错误信息,也可以通过perror来打印错误信息;

通过这个介绍我相信大家对errno已经有了一个初步的印象了,下面我们就来做个简单的测试,来看一下errno中的不同的值会对应哪些错误信息:

错误信息打印
可以看到这里我们测试的10个整型值都有其对应的错误信息。

3.8.3 perror

errno的介绍中还提到了一个函数perror,下面我们就来看一下这个库函数的相关内容:
perror
从函数的介绍中,我们可以获取以下信息:

  • perror函数是用来打印错误信息的;
  • perror的参数为空指针时,只打印错误信息;
  • perror的参数为非空指针时,会先打印字符串中的内容,并在后面加上冒号和空格后再打印错误信息;
  • perror应该在错误产生时立即调用,否则会被其它的信息给覆盖;

3.8.4 strerror、errno和perror的使用

下面我们通过一个简单的例子来说明一下strerror和perror这两个库函数以及errno这个宏常量应该如何使用,如下所示:

函数的使用1
在这次的测试中,我们仅仅执行了打印操作,此时程序在运行中是没有发生错误的,因此,errno的值为0,由strerrorperror这两个函数打印出来的错误信息来看,都是无错误;

函数的使用2
这次我们通过calloc函数向内存申请了1000000000个大小为整型大小的空间,可以看到,此时errno的值被calloc函数修改为了12,对应的错误信息为"Not enough space"——没有足够的空间。

从这两个例子中我们可以得到结论:

  • errno的作用就是用来实时监测程序运行的情况,当程序运行的过程中发生错误时,errno的值就会被修改;
  • 我们可以通过strerror来获取对应的错误信息字符串,如果要将这个信息打印出来,则需要借助输出函数来进行输出;
  • perror的作用就是自动获取错误信息并将错误信息打印在控制台上;

可见,相比于strerrorerrorperror不仅能够完成它们俩的工作,还能额外完成打印函数的工作。

结语

在今天的内容中,我们详细介绍了字符函数和字符串函数的相关知识点。下面我们简单的给今天的内容做个总结:

  • 在头文件<ctype.h>中包含了一系列的字符分类函数和字符转换函数;
  • 在头文件<string.h>中包含了一系列的字符串函数;
  • 字符串函数绝大部分都是来操作字符串的函数,如:
    • 求字符串长度的函数——strlen
    • 进行字符串拷贝的函数——strcpystrncpy
    • 进行字符串拼接的函数——strcatstrncat
    • 进行字符串比较的函数——strcmpstrncmp
    • 查找子串的函数——strstr
    • 进行字符串拆分的函数——strtok
  • 字符串函数中也有不是用来操作字符串而是用来获取错误信息字符串的函数——strerror
  • 包含在头文件<errno.h>中的宏常量errno可以获取错误信息;
  • 包含在头文件<stdio.h>中的库函数perror可以打印错误信息的;

今天的内容到这里就全部结束了,希望今天的内容能够掌握如何利用这些库函数更加高效的解决字符和字符串的问题。如果大家喜欢博主的内容,大家可以点赞、收藏加评论支持一下博主。当然也可以转发给身边需要的朋友。最后感谢各位的支持,咱们下一篇内容见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/588327.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Messari 报告摘要 :Covalent Network(CQT)2024 年第一季度表现

摘要&#xff1a; 尽管 CQT 代币流通供应量增加了 20%&#xff08;新增 1.04 亿枚 CQT&#xff09;&#xff0c;但 CQT 的质押百分比仅从 2023 年第一季度的 22% 增长到了 2024 年第一季度的 29%。 CQT 的市值季度环比增长了 28%&#xff0c;多次达到 2.75 亿美元&#xff0c…

脑筋急转弯在线问答

页面效果 点击“显示答案”按钮&#xff0c;显示参考答案。 页面代码 <% layout(/layouts/default.html, {title: 脑筋急转弯管理, libs: [dataGrid]}){ %> <div class"main-content"><div class"box box-main"><div class"bo…

【介绍下大数据组件之Storm】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【Java】 对象的比较【比较器】

登神长阶 第七阶 Java对象的比较 &#x1f3b7;一.Java对象的比较 &#x1fa97;1.基于引用的比较 基于引用的比较在Java中使用运算符进行。它主要检查两个对象是否引用内存中的相同位置。以下是基于引用的比较的详细介绍&#xff1a; 使用运算符&#xff1a; 运算符用于比…

【Qt QML】Frame组件

Frame&#xff08;框架&#xff09;包含在&#xff1a; import QtQuick.Controls继承自Pane控件。用于在可视框架内布局一组逻辑控件。简单来说就是用来包裹和突出显示其他可视元素。Frame不提供自己的布局&#xff0c;但需要自己对元素位置进行设置和定位&#xff0c;例如通过…

vue3与js的router基本使用方式

title: vue3与js的router基本使用方式 tags: vue3js abbrlink: ‘57270957’ date: 2024-04-17 18:54:47 第一步快捷引入的别名 使用路由需要大量在src文件中引用所需要的地址&#xff0c;并且组件中也需要很多的包的引用&#xff0c;将快速跳转到src这一文件的步骤进行简化操…

如何从 iPhone 恢复已删除或丢失的联系人?

不小心删除了您的 iPhone 联系人&#xff1f;不用担心。我们将向您展示如何从 iPhone或 iPad恢复已删除或丢失的联系人。当您从 iPhone 中删除联系人时&#xff0c;您可能认为无法将其恢复。但事实是&#xff0c;您可以从 iPhone 或 iPad 恢复已删除的联系人&#xff0c;因为它…

模型智能体开发之metagpt-多智能体实践

参考&#xff1a; metagpt环境配置参考模型智能体开发之metagpt-单智能体实践 需求分析 之前有过单智能体的测试case&#xff0c;但是现实生活场景是很复杂的&#xff0c;所以单智能体远远不能满足我们的诉求&#xff0c;所以仍然还需要了解多智能体的实现。通过多个role对动…

手撕spring框架(3)

手撕spring框架&#xff08;3&#xff09; 相关系列 手撕spring框架&#xff08;1&#xff09; 手撕spring框架&#xff08;2&#xff09; InitializingBean 接口详解 什么是 InitializingBean 接口&#xff1f; InitializingBean 接口是 Spring 框架中的一个接口&#xff0c…

【linux】进程(深入理解linux进程状态)

开始之前先说一个与本文无关的小知识&#xff0c;chdir命令可以更改当前进程的工作目录哦。 目录 linux具体进程状态&#xff1a;R && S&#xff1a;T && t&#xff1a;D&#xff1a;僵尸进程 && 孤儿进程&#xff1a; OS的理论线&#xff1a;运行&…

模型训练中的过拟合和欠拟合

基本概念 我们知道&#xff0c;所谓的神经网络其实就是一个复杂的非线性函数&#xff0c;网络越深&#xff0c;这个函数就越复杂&#xff0c;相应的表达能力也就越强&#xff0c;神经网络的训练则是一个拟合的过程。   当模型的复杂度小于真实数据的复杂度&#xff0c;模型表…

正版Office-Word使用时却提示无网络连接请检查你的网络设置 然后重试

这是购买电脑时自带的已经安装好的word。看纸箱外壳有office标记&#xff0c;但是好像没有印系列号。 某天要使用。提示&#xff1a;无网络连接请检查你的网络设置。 经过网上高手的提示&#xff1a; 说要勾选勾选ssl3.0、TLS1.0、1.1、1.2。 我的截图 我电脑进去就缺1.2. …

2024五一数学建模A题思路代码与论文分析

2024五一数学建模A题完整代码和成品论文获取↓↓↓↓↓ https://www.yuque.com/u42168770/qv6z0d/gyoz9ou5upvkv6nx?singleDoc# 2024五一数学建模A题钢板最优切割路径问题需要建立的模型和算法: 图论 最短路径算法(Dijkstra算法、Floyd算法等) 动态规划 网格化离散建模 …

Surya:强大的开源 OCR 文字识别工具

在当今数字化时代&#xff0c;文字识别技术扮演着至关重要的角色。VikParuchuri/surya 便是一款令人瞩目的开源 OCR 文字识别工具。 主要功能&#xff1a; 支持 90 多种语言的文字识别&#xff1a;Surya 具备强大的语言兼容性&#xff0c;能够轻松应对多种语言的文字识别任务&…

保存钉钉群直播回放下载:直播回放下载步骤详解

今天&#xff0c;我们就来拨开云雾&#xff0c;揭开保存钉钉群直播回放的神秘面纱。教会你们如何下载钉钉群直播回放 首先用到的工具我全部打包好了&#xff0c;有需要的自己下载一下 钉钉群直播回放工具下载&#xff1a;https://pan.baidu.com/s/1WVMNGoKcTwR_NDpvFP2O2A?p…

基于EBAZ4205矿板的图像处理:03摄像头采集HDMI输出视频图像

基于EBAZ4205矿板的图像处理&#xff1a;03摄像头采集HDMI输出视频图像 先看效果 项目简介 我是使用的EBAZ4205矿板&#xff0c;超级大电工的转接板和我自己买的一块没有xclk的ov5640完成的该项目&#xff0c;没有设备需自备。我就是跑通了正点原子的开源代码&#xff08;下文…

1991-2022年上市公司短贷长投/短债长用/投融资期限错配(包含原始数据及Stata代码)

01、数据简介 上市公司在投融资过程中&#xff0c;可能会涉及到投融资期限错配、短债长用和短贷长投等问题 投融资期限错配是指企业的资产与债务期限不匹配&#xff0c;主要表现为“短存长贷”&#xff0c;即资金来源短期化、资金运用长期化。当风险缓释的期限比当前的风险暴…

Elasticsearch:理解近似最近邻 (ANN) 算法

作者&#xff1a;来自 Elastic Elastic Platform Team 如果你是在互联网出现之前长大的&#xff0c;你会记得找到新喜好并不总是那么容易。我们是在无意中听到收音机里的新乐队时发现他们的&#xff0c;是因为忘了换频道偶然看到一个新电视节目的&#xff0c;也是几乎完全依据游…

本地搭建llama大模型及对话UI

环境说明&#xff1a;MBP 2023 M2Pro芯片 用到的工具/组件/技术&#xff1a;ollama、llama3:8b、docker、open-webui 1.下载ollama ollama官网下载地址&#xff1a;https://ollama.com/download 到ollama官网地址下载对应操作系统版本的ollama平台&#xff0c;按照安装指引…

unity制作app(2)--主界面

1.先跳转过来&#xff0c;做一个空壳&#xff01;新增场景main为4号场景&#xff01; 2.登录成功跳转到四号场景&#xff01; 2.在main场景中新建canvas&#xff0c;不同的状态计划用不同的panel来设计&#xff01; 增加canvas和底图image 3.突然输不出来中文了&#xff0c;浪…
最新文章