目录
文章目录
前言
函数模拟实现
strlen
strcpy
strcat
strstr
strcmp
memcpy
memmove
总结
继接上文,本片文章我将带领大家去模拟实现一些基本的库函数。
前文我们已将基本了解了strlen函数是用于计算字符串长度的,那么接下来我们来模拟实现一下该函数
strlen函数统计的是\0之前的字符个数,函数返回类型是int,这里我们有三种实现方法:
方法一:计数器
int my_strlen(const char *ch)
{
int i = 0;
while(*ch != '\0') {
ch++;
i++;
}
return i;
}
方法二:递归
int my_strlen(const char * str)
{
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}
方法三:指针运算
int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
这些方法的代码实现都较为简单,就不再细介绍。
在模拟实现strcpy函数时需要注意以下几点:
我们再来看一下函数的原型:
char* strcpy(char * destination, const char * source );
注意const修饰的参数模拟实现代码:
char *my_strcpy(char* dest, const char* src)
{
assert(src&&dest);
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
assert断言,为了防止传进来为NULL,后置++的优先级高于*,所以在*dest++中先后置++,再解引用。当赋值到\0时循环就会结束,返回目标字符串的起始位置。
函数的原型与strcpy函数原型类似。
strcat函数的功能是连接两个字符串,我们可以依据strcpy的思路将源字符串的内容copy到目标字符串的末尾,我们只需找到目标字符串中\0的位置即可
代码实现:
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}
那为什么strcat最好不要自己给自己追加。
看到模拟实现的strcat我们或许就会发现问题:
如果自己给自己追加,初始时dest和src就指向同一个字符串,当dest 向后移动找到\0以后就会停止(需要追加的位置),然后就会开始追加,将src指向的字符赋给dest指向的位置,这就会把\0给替换掉:
但是\0对于src来说又很重要,这是循环结束的标志,而此时\0被替换,循环就无法停止,便会一直向后赋值,知道越界访问程序崩溃停止。
strstr函数的功能是在一个字符串中查找指定的子串,并返回该子串在原字符串中的起始位置。
函数原型:
char* strstr(const char* str1, const char* str2);
strstr在不依靠库函数的情况下,对其模拟实现可能有点复杂。
具体代码:
char* my_strstr(char* str1, char* str2)
{
char* s1 = str1;
char* s2 = str2;
char* cp = str1;
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 && * s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return cp;
cp++;
}
return NULL;
}
以a b b b c d e f和b b c为例:
初始时两指针指向两字符串的首元素地址,问题在于要确定开始匹配的位置,那么我们就需要一个指针来存放开始匹配的位置,我们设为cp,初始阶段开始匹配,cp指向的位置和str1相同。
cp不断的向后寻找开始匹配的位置,如果*cp=\0,就说明cp遍历了整个字符串都没有找到匹配点,说明str2不属于str1的子字符串,返回NULL。
以此为前提才能开始匹配。从第一个字符开始向后匹配,匹配失败后我们就需要移动cp向后移动尝试从下一个位置开始,所以进行cp++;
s1和s2是进行字符比对的指针,起初s1和s2都是从字符串的首元素开始:
然后s1和s2同步进行移动,对字符进行一一比对,
while (*s1 == *s2)
{
s1++;
s2++;
}
如果匹配失败,cp就向后移动,s1从cp指向的位置开始继续比对(s1=cp),s2从str2指向的字符串的首元素开始(s2=str2),如果匹配成功,此时的s2指向的一定是字符串末尾的\0由此我们就可以以此为判定条件,判断是否匹配成功:
while (*s1 && * s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return cp;
匹配成功就可以提前结束程序,返回首次匹配成功的初始位置(return cp)。遍历结束都没有找到则返回空指针。
strcmp函数的功能是比较两字符串大小。
具体功能:
由此我们来模拟实现一下,首先我们依次比对两个字符串对应位置字符的大小。如果有一个遍历结束,就返回0。但切记while循环里不可以这样写*str1++ == *str2++。
如果str1和str2不相等退出循环后仍会向后走一步,这样就跳过了对应位置上第一个不相等的字符,就会导致在判断大小时出现错误。
所以我们选择在循环里进行++操作。
在两字符不相等的前提下,如果str1指向的字符大于str2指向的字符,就返回1,否则就返回-1
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;
函数原型:
void * memcpy ( void * destination, const void * source, size_t num );
从source的位置开始向后复制num个字节的数据到destination的内存位置。
此外我们还要注意:
这里注意,memcpy的函数类型是void* 类型,这表明memcpy函数可以用于所以类型的数据。
那么在模拟实现时,我们就要设计一个可以适用于任何类型的函数。最终的返回结果是目标dest数据的起始地址。
于是我们就可以这样设计
模拟代码演示:
void* my_memcpy(void* dest, void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
num为需要copy的字节大小,可以作为循环的次数,不管传入任何类型的数据,我们都将其转换为char*类型,进行一个字节一个字节的复制。最终返回目标dest数据的起始地址。只需创建一个变量存储即可。
函数原型:
void* memmove(void* dest, const void* src, size_t n);
dest是目标内存的起始地址,src是源内存的起始地址,n是要移动的字节数
注意:函数原型中的参数类型都是void*,表示传入的参数是任意类型的指针,可以用于处理不同类型的数据。返回值类型是void*,表示返回一个指向目标内存的指针。
那么我们要如何设计呢?
有人可能会想,这个简单,我们模仿上边的memcpy函数进行复制就好了,但这样真的可以吗?
在模拟的memcpy中确实可以实现部分的数据移动,但它并不完善。这里我们需要考虑到数据覆盖问题。
如果是将数据从高地址移动到同一数组的低地址处,这样可以实现如:
memcpy(arr, arr + 2, 20);
但如果是将前边的数据向后移动呢?
memcpy(arr+2, arr , 20);
当我们想把arr处20个字节的数据向后移动2个单位时,1赋值给了3,2赋值给了4,那到3赋值时3的值就已经被覆盖,这就会导致最终结果错误。
那我们应该怎么解决呢?
这时我们就可以考虑从源数据段的最后边开始赋值,20个字节,5个元素,先将5赋给7,4赋给6,3赋给5……
这样就可以实现向后移动。
回到memmove函数,它是一个可以接收任何类型数据的函数,那么次时我们就需要将数据类型转换为char*类型,进行一个字节一个字节的赋值。
代码实现:
my_memmove(void* dest, const void* src, size_t n)
{
if (dest < src)
{
while (n--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (n--)
{
*((char*)dest+n) = *((char*)src+n);
}
}
}
根据src和dest的大小进行判断,选择合适的方法。
(char*)dest+n和(char*)src+n将两个指针移动到需要后移的数据最后,从后向前依次赋值。每移动一个字节,n--,dest和src从最后向前一个字节,继续赋值……,直到需要移动的字节移动完毕停止。
好的本期内容到此结束,模拟实现这些内容虽然很不常用,但是却可以帮助我们更好的理解和正确的使用这些库函数,同样也可以帮助我们提升一定的算法能力,对于处理一些字符操作题目很有帮助。最后,感谢阅读!