C语言的库函数虽然不算多,但若能熟练掌握一部分,或者说能学会去了解库函数的使用,无论是对C语言的使用熟练程度还是自己代码能力的提升都是有帮助的。所以,本篇文章旨在向读者展示如何了解并熟练使用一个库函数,本篇文章以头文件string.h
中的一部分库函数为例讲解。
在讲解前先给你们展示一下如何使用一些辅助工具,也就是网站,来了解库函数。第一个网站是cplusplus.com,这个网站有c++各种库函数,关键字等的讲解,当然,因为C语言和c++是兼容的,所以C语言内容同样是有收录的。如下图所示是该网站的操作界面,这是beta版也就是新版的操作界面,由于没有搜索框,对不熟悉的人来说有一定使用难度,所以可以点击我在图中标记的Legacy version前往旧版网站。
(旧版网站主界面如下)
当我们想查询库函数时只需要在搜索框输入函数名即可。第二个网站是cppreference.com,使用方法与第一个网站差不多,除此之外这个网站是有中文版的,C++ 参考手册。实在看不懂英文可以拿中文版对照一下。
接下来以第一个网站为例,当我们想要知道头文件string.h
包含哪些库函数时,我们可以在搜索框输入string.h
,就可以来到下图界面。
可以看出,该头文件被以函数功能,关键字,类型的分类排版展示,点击想查看的函数就可以查看该函数具体信息。当然,通过搜索框搜索函数名也可直接跳转至函数详情页。下图展示函数strlen
的详情。
可以看到在strlen
的下方是函数的声明,再下方的一大段英文是strlen
具体使用规则,功能解释,使用说明,下方的几栏算是对该段英文的摘要,补充说明或者举例说明,parameters栏即参数说明,return value栏为返回值说明,剩下的那个自然是举例说明。
这就是当我们想要初步了解一个库函数时所需要的辅助工具,想要进一步了解函数仅仅停留在看文档的地步是不够的,我们还需要初步使用该函数测试一些自己阅读完文档后想出来的一些针对性的用例。当然,在这之后还有一个最好的方法,也就是本篇文章的重头戏——模拟实现库函数。接下来我将逐个讲解并模拟实现一部分string.h
中的函数。
先看看网站上strlen
函数的使用说明。
先看函数声明,返回值是size_t类型的,也就是unsigned int
类型,函数参数是const
修饰的char*
,是一个字符指针。接下来说明使用这个函数会得到字符串的长度也就是调用函数是返回字符串的长度。接下来说明这个函数是依据空字符看待字符串长度的,也就是说,从第一个字符开始,到遇到’\0’,这之间的长度(不含’\0’)就会被认为是这个字符串的长度,(当然,如果这个字符串没有’\0’,这个函数也会越界访问,直到遇到某处的’\0’才停止计数)。总结起来就是这个要点:
const char*
size_t
'\0'
停止长度统计初步了解函数底层实现逻辑后我们就可以着手实现了。
代码实现:
size_t strlen_simulation(const char* str)
{
assert(str);
size_t len = 0;
while (*str++)
len++;
return len;
}
文档上的使用说明:
函数有两个char*
类型参数,名字也很清晰,根据Parameters栏上的解释,一个是复制来源,一个是复制目标,函数返回一个char*
的值,根据Return value栏上的解释,返回复制来源内容的目标字符串,也就是destination
。而根据具体使用说明,该函数功能是将来源字符串的内容复制到目标字符串且包括终止标识符'\0'
,并且为了避免越界访问,目标字符串的长度必须长或等于来源字符串的长度(当然,这里计算长度也将’\0'
计入在内)。除此之外,来源字符串的空间和目标字符串的空间不能重叠,这是一个重点,也由此看出这个函数是直接一个一个地址拷贝而不是预先拷贝整份数据用一个临时变量保存再拷贝到目标地址。(下图为使用重叠空间出现的错误,明显已经越界访问并且出现死循环了)
所以要模拟实现strcpy
有以下几个要点:
char*
类型的函数参数,分别代表复制来源的地址和复制目标的地址,且函数设计不考虑两块地址存在空间重叠的问题char*
的地址,为复制目标的地址,通过这个地址可以访问得到复制成功后的内容'\0'
计入长度并参与复制,空间重叠时存在循环,说明是'\0'
在控制循环
代码实现:char* strcpy_simulation(char* strD, const char* strS)
{
assert(strD && strS);
char* ret = strD;
while (*strD++ = *strS++)
;
return ret;
}
使用说明:
函数参数是两个由const
修饰的char*
指针,函数比较两个指针指向的空间的内容。函数的返回值一个整型的值,这个返回值是由两个字符串的关系决定的,当两个字符串相同时则返回0,而不相同时大于0则说明第一个字符串比第二个字符串的值更大,小于0则更小,而这个值其实是比较时遇到的第一个不相同的字符的值的比较。也就是说,只需要判断函数返回值是否为零就能判断传参的两个字符串是否相同。同时,这个说明也讲了这个函数是如何运作的。该函数从两个字符串的第一个字符开始比较是否相等,当遇到比较到不同的字符或者遇到终止字符'\0'
时停止比较,并且这个函数比较的是字符的二进制值(参考ascii码值表)。所以模拟实现根据以下要点:
const
修饰的char*
代码实现:
int strcmp_simulation(const char* p1, const char* p2)
{
assert(p1 && p2);
while (*p1 == *p2)
{
if (*p1 == '\0')
return 0;
p1++;
p2++;
}
return *p1 - *p2;
}
使用说明:
函数参数是两个char*
类型的指针,其中一个有const
修饰。按照解释,destination
指向一个含有字符串的字符数组,并且这个数组的大小能容纳新连接的字符串,而destinaion
指向的字符串后面需要的连接的字符串内容就在source
指向的字符数组之中,并且source
指向的空间与destination
指向的空间不能重叠。按照说明,函数的功能是将source
指向字符串的一份拷贝连接到destination
指向字符串的结尾,destination
指向字符串结尾的'\0'
会被覆盖,且在连接完成后的新字符串结尾会带有'\0'
。最后,函数会返回指向新字符串的指针,也就是指针destination
。有以下要点:
char*
,一个const char*
destination
指向字符串的是一份字符串的拷贝,也就是不改变source
指向的字符串destination
指向字符串的'\0
’会被覆盖,新字符串用source
指向字符串的'\0'
,也就是拷贝时连带’\0'
一起拷贝代码实现:
char* strcat_simulation(char* sd, const char* ss)
{
assert(sd && ss);
char* ret = sd;
while (*sd)
sd++;
while (*ss)
*sd++ = *ss++;
return ret;
}
使用说明:
函数参数有三个,void*
的指针,const void*
的指针以及size_t
类型的值,其中,destination
指向存放复制内容的地址,source
指向被复制内容的空间,num
的值被复制内容所占的空间大小,单位是字节。 函数的功能是从source
指向空间复制num
个字节的内容到destination
指向的空间,并且这个函数因为是一块一块字节的复制空间上的内容,所以并不考虑这两个指针参数实际的类型关系。这个函数是固定操作num
个字节的空间的,不会检测终止标识符。同样的,为了避免越界,这两个指针所指向的空间必须多于num
个字节,并且两块空间不能有重叠。函数最终会返回一个void*
的指针,指向复制完成的内容,也就是destination
指向的空间,使用时需自己进行类型转换。
模拟实现要点总结:
void*
的指针,const void*
的指针以及size_t
类型的值destination
指向的空间num
个字节空间,也就是可以用num
控制循环代码实现:
void* memcpy_simulation(void* dest, const void* src, size_t n)
{
assert(dest && src);
void* ret = dest;
while (n--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
使用说明:
函数参数和返回值的设置意图和memcpy
基本相同,唯一一点不同的是不要求两个指针指向的空间不能重叠,相应的,这个函数的功能与memcpy
其实也基本相同,但是能处理两个指针指向空间重叠的情况。但是,虽然上文说memcpy
不能处理空间重叠的情况,但是其实在有些编译器上是可以做到的,比如笔者用的vs编译器。这是由于研发编译器的程序员自行优化的,在这种情况下,memcpy
和memmove
的功能其实相差无几了。
模拟实现要点总结:
void*
的指针,const void*
的指针以及size_t
类型的值destination
指向的空间num
个字节空间,也就是可以用num
控制循环代码实现:
void* memmove_simulation(void* dest, const void* src, size_t n)
{
assert(dest && src);
int i = 0;
if (dest >= src)
{
for (i = n - 1; i >= 0; i--)
{
*((char*)dest + i) = *((char*)src + i);
}
}
else
{
for (i = 0; i < n; i++)
{
*((char*)dest + i) = *((char*)src + i);
}
}
return dest;
}
使用说明:
函数参数和返回值和strcat
差不多,只是多了个size_t
的值,这num
的值的功能是将source
指向的字符串的的num
个字符连接到destination
指向字符串,不再以'\0'
为结束标志。所以函数功能上的差异也只是从连接一整串字符串变成了连接部分字符串。但是如果字符串本身的长度就小于num
,连接时遇到终止标识符同样会停止拷贝内容。
模拟实现要点:
char*
,一个const char*
destination
指向字符串的是一份字符串的拷贝,也就是不改变source
指向的字符串destination
指向字符串的'\0
’会被覆盖num
大于source
指向字符串长度时,会以'\0'
为停止标志且'\0'
会被连接上,而小于时,为确保新字符串有停止标识,需自行加上'\0'
代码实现:
char* strncat_simulation(char* dp, char* sp, size_t num)
{
assert(dp && sp);
char* ret = dp;
while (*dp)
dp++;
while (num-- && *sp)
{
*dp++ = *sp++;
}
*dp = '\0';
return ret;
}
使用说明:
这个函数与strcpy
的区别也是从复制一整串字符串到复制部分字符串,用num
的值控制复制字符个数。对于停止标志的处理与strncat
相同。
模拟实现要点:
char*
类型的函数参数,分别代表复制来源的地址和复制目标的地址,且函数设计不考虑两块地址存在空间重叠的问题char*
的地址,为复制目标的地址,通过这个地址可以访问得到复制成功后的内容num
大于source
指向字符串长度时,会以'\0'
为停止标志且'\0'
会被连接上,而小于时,为确保新字符串有停止标识,需自行加上'\0'
代码实现:
char* strncpy_simulation(char* dest, char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
while (num--)
{
char tmp = *dest;
*dest = *src;
*src = tmp;
dest++;
src++;
}
return ret;
}
使用说明:
这个函数声明在文档上有两种形式,但下方Portability栏有解释,在C语言中的声明只有栏中这中。函数参数为两个const char*
,其中str1
指向被检视的字符串,str2
指向一串有序的字符串(用于在str1
中匹配确认)。返回值为char*
,如果str2
指向字符串在str1
指向字符串中有出现,则该返回指向str1
中出现str2
所指字符串内容的位置的指针,如果没有出现则返回空指针。很明显,这是一个检查在一个字符串中是否出现另一个字符串内容的函数。此外,在匹配过程中,str2
中的'\0'
不计入匹配但会作为停止标志。
模拟实现要点:
const char*
char*
,如果匹配成功返回匹配成功位置,失败则返回空指针'\0'
不计入匹配内容,但会作为停止标识符代码实现:
char* strstr_simulation(const char* p1, const char* p2)
{
assert(p1 && p2);
while (*p1)
{
const char* tmp2 = p2;
const char* ret = p1;
const char* tmp1 = p1;
while (*tmp1 == *tmp2 && *tmp1 != '\0'&& *tmp2 != '\0')
{
tmp2++;
tmp1++;
}
if (*tmp2 == '\0')
return (char*)ret;
p1++;
}
return NULL;
}
string.h
的一部分库函数我就先讲到这了,后面可能还会出博客讲解其他库函数。这篇博客的主要目的还是讲讲如何了解熟悉一个库函数。非常感谢各位读者能读完这篇文章,如果你觉得做的还不错的话,可以点赞收藏分享,让更多的朋友知道,当然,如果你觉得有什么问题的话也欢迎在评论区留言或私信告诉我哦!下期再会!
本篇文章的代码我上传到仓库里去了,文件名为string.h_simulation,有需要自取。 传送门:gitee github