我们知道,编译器给我们提供了很多实现不同常用功能的库函数,它们给我们提供了很多便利。
但是怎么说它们都是别人家的函数,我们能不能自己来实现,把它变成我们自家的函数呢?
当然可以!只有想不到,没有做不到!
下面我们就来一个个攻破它们吧!
首先我们看看我们今天要掌握的函数有哪些吧~
- 求字符串长度
strlen- 长度不受限制的字符串函数
strcpy
strcat
strcmp- 长度受限制的字符串函数介绍
strncpy
strncat
strncmp- 字符串查找
strstr
strtok- 错误信息报告
strerror- 内存操作函数
memcpy
memmove
memset
memcmp
strlen - 字符串长度
我们传给函数一个字符串的地址,它负责帮我计算字符串的长度。
- 字符串以’\0’结束,而strlen返回的是字符串中在’\0’之前出现的字符个数
- 传给函数的字符串应该以’\0’结束,否则返回的是一个随机值。(因为strlen会一直向后访问直到遇到’\0’为止)
- strlen返回值类型是size_t,一个无符号整型。
大家思考一下,下面的程序会输出什么值?
#include
#include
int main()
{
if (strlen("abc") - strlen("abcdef") > 0)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}
strlen(“abc”)的值比strlen(“abcdef”)的值小,所以屏幕中应该输出“haha”,是这样的吗?
大家要注意,strlen这个函数的返回类型是size_t,无符号整型,而无符号整型计算的结果应该是>=0的,所以程序最终输出的应该是“hehe”。
我们通过调试中的监视窗口可以看到,它其实是一个很大很大的正数。
弄懂了strlen函数之后,我们就来开始我们的第一个模仿创造之旅,写一个我们自己的strlen函数吧~
下面我们把自己模拟实现的strlen函数称为my_strlen函数。
首先我们写出my_strlen函数的使用场景。
然后进行函数的封装。
其实除了上面这种方法外,还有下面这两种方法。
strcpy - 字符串拷贝
我们传一个目标字符串和源字符串给函数,让它帮我们把源字符串中的字符拷贝到目标字符串中。
- 源字符串必须以’\0’结尾。
- strcpy函数会把源字符串中的’\0’也拷贝到目标字符串中。
- 目标字符串的必须可变。
- 目标字符串的空间必须足够大。(确保能放源字符串)
strcpy的模拟实现我们在之前的文章中已经详细讲过,这里就直接上代码啦!
详情请看学了编程却写出错误代码?程序运行结果与想象不符?当bug出现时该何去何从,别担心,这篇文章统统告诉你!手把手带你调试代码,让bug原形毕露!常见的代码技巧这一节。
strcat - 字符串连接
我们把目标字符串和源字符串一起传给函数,函数帮我们把源字符串接到目标字符串的后面。
- 源字符串必须以‘\0’结尾。
- 目标字符串必须可被修改。
- 目标字符串的空间应该足够大。
- 谨慎用strcat自己给自己追加。(自己给自己追加时,程序会覆盖掉’\0’,导致无限追加,程序崩溃)
strcmp - 字符串比较
我们把两个字符串传给函数,由函数帮助比较两个字符串的大小。
注意:我们这里比较的是字符串的内容,而非长度。
由上图,可以看到函数strcmp的返回值:
如果两个字符串相同,则返回0。
如果两个字符串不同,则比较第一个不同的字符的值,如果pt1的值小于ptr2的值,则返回一个<0的数,否则返回一个>0的数。
上面的函数是长度不受限制的,所以我们传多长的字符串给函数,函数就会一直进行相应的操作知道达到了结束的条件。所以我们说这样的函数是不够安全的。
所以,库中又有了一些长度受限制的函数,它们相对来说就被认为是比较安全的函数。
strncpy
strncat
strncmp
我们可以看到,这三个函数相对于前面的三个函数,函数名中间都多了一个n,而这个n实际上就是num,即操作的字符个数。
拷贝num个字符到目标字符串中。
如果我们想拷贝的字符数比源字符串长度大,则超出来的部分会默认填充’\0’。
比较num个字符。
strstr - 字符串查找
用于在一个字符串中查找子字符串,返回的是子字符串在该字符串中第一次出现的位置,如果不包含该子字符串,则返回NULL。
注意,strstr是查找ch2在ch1中第一次出现的位置。所以如果ch2在ch1中出现了两次,则只返回第一次出现的位置。
首先我们应该考虑到查找字符串时可能出现的两种情况。
是则p1和p2均++。
情况1相对较为简单,一次查找就能找到。
下面我们看情况2。如果还是按照情况1那样往下走,当遇到下面这种情况之后,我们应该怎么办呢?
这时候p2应该回到起始位置,同时p1应该回到p1开始查找的下一个位置,再重新进行查找。
所以我们应该先把这p1和p2要回归的那个位置保存下来。
当p1往前走时,s1也要跟着往前走。
当p1和p2相等时,查找开始,s1应该记录下p1开始查找的位置的下一个位置。
这样当p1和p2不匹配时,p1就可以回到s1的位置重新开始查找。
下面我们用代码实现my_strstr。
strtok - 字符串切割
把由分隔符分割的字符串分割成各个部分。
例如:123.111.222.383
按照.分割为
123
111
222
383
那么这个函数到底是怎样的呢?
- 参数delimiters指的是用作分隔符的字符集合。
- str指向的是一个被0个或多个分隔符分割的字符串
- strtok函数找到str中的下一个标记,并将更改为’\0’,同时返回一个指向这个标记的指针。(注意:因为strtok函数会改变str,所以我们传参时一般传的是一份临时拷贝并且可修改的内容。)
- 当strtok函数的第一个参数str不为NULL时 ,函数将找到str中的第一个分隔符,并保存它在字符串中的位置。
- 当strtok函数的第一个参数str为NULL时 ,函数从str中上次保存的位置开始,查找下一个分隔符。
- 当找不到分隔符时,strtok会返回NULL。
说了那么多,不知道看懂了没?
那么像这样一段代码,我们可以用一个循环来表示,会显得更加好理解。
我们可以发现,每一次调用完strtok之后,函数会保存上一次函数调用时访问的位置。
这说明该函数中应该含有一些静态变量或者全局变量,函数调用完之后,这些变量并没有随着函数栈帧的销毁而销毁,从而使得strtok函数具有“记忆功能”。
当然由于使用了这个函数的实现是比较复杂的,并且相对来说并不常用,所以这里博主就没有尝试把它变成自家函数啦!有幸的uu们可以自己尝试一下哈!
strerror - 错误信息
C语言中,我们会对一些常见的错误进行编号,并给上一个错误码,当我们把错误码传给strerror时,函数就会返回我们错误信息,告诉我们是哪里出了错。
我们可以看到每个错误码对应的错误信息,但是我们应该如何获得错误信息呢?
当库函数调用失败的时候,就会返回一个错误码。
所以,strerror是一个可以返回C语言内置的错误码对应的错误信息的函数。
此外,还有一个strerror的进阶版:perror,可以认为它==printf + strerror,即打印错误信息。
memcpy - 内存拷贝
之前我们学过strcpy - 字符串拷贝,它只能对字符串进行操作,那么现在我们看看内存拷贝,它是对内存进行操作的,也就可以对各种类型的变量进行操作。
我们调试来看看。
我们可以看到arr并没有变成我们希望的样子,因为前面要被拷贝的元素被覆盖了。
但是在C语言中,我们对memcpy的要求是只要完成了不重叠的内存拷贝就ok。(尽管现在一些编译器的memcpy也可以完成重叠的内存拷贝)
那么对于内存重叠的情况,我们应该怎么办呢?
这时候我们就要提一提memmove这个函数啦!
#memmove
memmove - 内存移动
我们可以看到,memmove的参数和返回类型和memcpy实际上是一样的,但是它既可以完成不重叠内存的拷贝,也可以完成重叠内存的拷贝。
那么我们要实现内存重叠的拷贝,应该如何实现呢?
为了防止拷贝时重叠的内存块被覆盖,我们应该在内存被覆盖之前就将其拷贝到目标位置的,这里分两种情况。
那么我们具体应该怎么做呢?
从前向后拷贝,应该与memcpy的实现是一样的。
我们再来看看从后向前拷贝。
最后,把返回值写上,我们的my_memmove就完成啦!
下面我们来验证一下~
NICE!下一个!
memset - 内存设置函数
我们可以通过memset把指定内存中的内容放入我们指给定的值。
快来评论区!!!让我们知道你学会多少个库函数~~
其实库函数中关于字符串和内存的函数还有很多,大家感兴趣的可以一一去研究一下~
我们今天的内容就分享到这里啦!
如果你喜欢我的文章,别忘了点赞收藏加关注噢~
关注我,一起精进C语言!
本文相关代码已经上传至gitee啦!欢迎取阅~
https://gitee.com/fang-qiuhui/my-code/blob/ef5606d87a73e595436453a7e4b1ab0a0ff07bcd/practice_2021_9_21_Library_Functions/practice_2021_9_21_Library_Functions.c