前言
C语言本身自带许多的库函数,通过包含相应的头文件就可以直接使用。
学习如何使用库函数是学习C语言重要的一环,其中最常用的一类当属字符串库函数。
接下来介绍最常用的几种字符串库函数以及他们的模拟实现。
从三个方面来介绍函数:
1.参数
2.函数功能
3.返回值
#1 strlen
传递一个字符指针。
计算字符串的长度。
返回该长度。
//strlen的基本格式、
size_t strlen ( const char * str );
Tips:
1.字符串以符号 ‘\0’ 作为结束标志,计算的是 ‘\0’ 之前的字符数量(不包含 ‘\0’ 本身)。
2.如果作为参数的字符串内不包含 ‘\0’ 那么就会越界访问,最后计算出随机值。
3.函数的返回值类型为 size_t ,无符号整形。
模拟实现:
任何对于空指针的解引用都会导致错误。
所以在需要对指针解引用的场合,都建议加上assert 来判断。
//内部计数器
size_t my_strlen(const char* str)
{
assert(str);
size_t count = 0;
if (*str != '\0')
{
count++;
str++;
}
return count;
}
#2 strcpy
两个字符指针作为参数,分别指向目标空间和源字符串。
复制字符串,将源字符串 source 拷贝到目标空间 destination 中。
返回指向目标空间的指针。
//strcpy的基本格式
char* strcpy(char * destination, const char * source );
Tips:
1.源字符串必须以 ‘\0’ 结尾,否则同样会造成越界访问。
2.因为是拷贝字符串,所以也会将结果的 ‘\0’ 拷贝到目标空间中。
3.注意目标空间需要足够大。
模拟实现:
char* my_strcpy(char* destination, const char* source)
{
assert(destination);
assert(source);
//保留初始destination所指向的位置
char* ret = destination;
while (*source != '\0')
{
*destination = *source;
destination++;
source++;
}
*destination = *source;
return ret;
}
#3 strcat
两个字符指针作为参数,分别指向目标空间和源字符串。
将源字符串拷贝到目标空间内原有字符串之后。即字符串的追加。
返回指向目标空间的指针。
//strcat的基本格式
char * strcat ( char * destination, const char * source );
Tips:
1.源字符串必须以 ‘\0’ 结尾。
2.追加操作会覆盖目标字符串最后的 ‘\0’ ,保证最终合并的字符串也只有最后的 ‘\0’ 。
3.目标空间需要足够大,能够容纳追加后的长度。
模拟实现:
char* my_strcat(char* destination, const char* source)
{
assert(destination);
assert(source);
char* ret = destination;
//先找到 '\0' 的位置,这就是追加开始的位置
while (*destination != '\0')
{
destination++;
}
//后续就和strcpy一致了
while (*source != '\0')
{
*destination = *source;
destination++;
source++;
}
*destination = *source;
return ret;
}
#4 strstr
传递两个字符指针,一个作为母字符串,一个作为子字符串。
判断母字符串中是否包含子字符串。
如果有,返回指向子字符串在母字符串中第一个字符的指针;否则,返回空指针。
//strstr的基本格式
char * strstr ( const char *str1, const char * str2);
两个字符串当然都要以 ‘\0’ 结尾。
模拟实现:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
//如果子字符串仅仅只有一个'\0',那么也视作包含,返回母字符串的首字符位置即可。
if (*str2 == '\0')
{
return (char*)str1;
}
//只要head还不是 '\0',就说明母字符串没走到头,循环就继续
char* head = (char*)str1;
while (*head != '\0')
{
//保存两字符串的起始位置
//母字符串每次不从初始位置开始走,而是从head开始走
char* s1 = head;
char* s2 = (char*)str2;
//两个字符相同且不为'\0',循环就继续,判断下两个字符
while ((*s1 != '\0') && (*s2 != '\0') && (*s1 == *s2))
{
s1++;
s2++;
//当子字符串走到头了,说明子字符串的确包含在母字符串内
if (*s2 == '\0')
{
return head;
}
}
//出这个循环不代表不包含,可能只是起始位置不对.让起始位置向下走
head++;
}
//出这个循环才说明真的不包含,此时返回空指针
return NULL;
}
#5 strcmp
两个字符指针。一号字符串与二号字符串。
判断两个字符串大小。
如果一号字符串大于二号字符串,返回大于0的数字。
如果相等大小,则返回0.
如果一号字符串小于二号字符串,返回小于0的数字。
//strcmp的基本格式
int strcmp ( const char * str1, const char * str2 );
Tips:
1.比较两个字符串大小,是看两个字符串中字符的ascll码值的大小,而不是比较长度。
例如abc就小于qp,因为a的ascll码值小于q。
2.如果第一位符号相同,就比第二位,以此类推。
模拟实现:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
//如果两个字符相同且不为'\0',就继续比较
while ((*str1 == *str2) && (*str1 != '\0'))
{
str1++;
str2++;
}
//出上面循环有两种情况
//第一种,两个符号不同
if (*str1 != *str2)
{
//判断大小,返回相应的值
if ((*str1 - *str2) > 0)
{
return 1;
}
else
{
return -1;
}
}
//第二种,都走到头了,说明两字符串相同
return 0;
}
#6 memcpy
三个参数,两个任意相同类型指针,一个无符号整形。指针分别指向目标空间和源空间。
将源空间的前几个字节复制到目标空间,具体多少个取决于无符号整形参数。
返回值为指向目标空间起始位置的指针。
//memcpy的基本格式
void * memcpy ( void * destination, const void * source, size_t num );
Tips:
1.memcpy并不局限于字符串,指针参数类型和返回值类型都为void*,意味着可以接收和返回任意类型的指针。
2.与strcpy不同的是,memcpy并不会在遇到 '\0’的时候停下,所以需要额外传参确定复制多少字节。
3.源空间与目标空间不能有任何的重叠。如果有重叠,就可能导致不完整的复制。
模拟实现:
void* my_memcpy(void* destination, const void* source, size_t num)
{
assert(destination);
assert(source);
void* ret = destination;
while (num--)
{
*(char*)destination = *(char*)source;
destination = (char*)destination + 1;
source = (char*)source + 1;
}
return ret;
}
#7 memmove
参数及类型与memcpy完全一致。
//memmove的基本格式
void * memmove ( void * destination, const void * source, size_t num );
与memcpy的区别只在于memmove处理的源空间和目标空间是可以重叠的。
所以如果有这种场合,就必须使用memmove而不是memcpy。
模拟实现:
void* my_memmove(void* destination, const void* source, size_t num)
{
assert(destination);
assert(source);
void* ret = destination;
//判断空间是如何重叠的
//如果目标空间的的尾部与源空间的头部重叠,或者根本不重叠,那就和memcpy一致
if ((destination < source) || ((char*)destination >= (char*)source + num))
{
while (num--)
{
*(char*)destination = *(char*)source;
destination = (char*)destination + 1;
source = (char*)source + 1;
}
return ret;
}
//否则就得倒着来复制,不然源空间的尾部数据还没有来得及复制就会被覆盖,导致结果不符合预期
else
{
(char*)destination = (char*)destination + num - 1;
(char*)source = (char*)source + num - 1;
while (num--)
{
*(char*)destination = *(char*)source;
destination = (char*)destination - 1;
source = (char*)source - 1;
}
return ret;
}
return ret;
}
结尾
有更多没有提及的库函数,这里展现的仅仅只是冰山一角。
以及模拟实现函数的方法可能不止一种,文中也只展现了一种。
至于,为什么要进行库函数的模拟实现?
比方说,如果没有仔细了解过strlen,可能会认为返回值是整形 int ,这就会导致在某些场合错误的使用库函数。
通过模拟实现,我们更深刻的重新认识这些库函数,或发现了以前没有注意过的细节,或加深了记忆,都有利于以后得心应手的使用。
以上。