2.库函数的模拟实现
目录
以下三个需轻松手撕:
1. memcpy
2. memmove
3. strstr
下面三个出现的相对较少:
1.strlen
2. strcpy
3. strcmp
以下三个需轻松手撕:
1. memcpy
void *my_memcpy(void *dest, const void *src, size_t count)
函数memcpy从src的位置开始向后复制count个字节的数据到dest位置。这个函数在遇到 '\0' 的时候并不会停下来。如果src和dest有任何重叠,复制结果都是未定义的。
-
dst
: 目标内存区域的指针,表示数据将要被复制到的位置。 -
src
: 源内存区域的指针,表示数据来源的位置。 -
count
: 要复制的字节数。
首先要有一个返回变量 ret,保存目标内存区域的起始地址,因为后面会对 dst++,防止这个位置丢失。
然后断言 dst 和 src 都不为空。
然后开始循环复制count个字节,也就是走 count 步,while(count--)。
在循环中,要将src指向的内容复制到dst中,然后各自移向下个字节。
void *my_memcpy(void *dst, const void *src, size_t count)
{
void* ret = dst;
assert(dst && src);
while (count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1 ;
src = (char*)src + 1;
}
return ret;
}
- 由于
void*
类型不能直接解引用,因此需要将其强制转换为char*
类型(因为char
类型的大小为一个字节,适合逐字节复制)。 -
void*
不能直接进行指针算术运算(如+
或-
),因为void
类型没有大小。 -
必须将其转换为具体类型的指针(如
char*
)才能进行指针算术运算。
为什么 dst
和 src
是 void*
类型?
-
void*
是一种通用指针类型,可以指向任何类型的数据(如char
、int
、struct
等)。 -
通过使用
void*
,my_memcpy
函数可以处理任意类型的内存区域,而不需要为每种数据类型编写单独的函数。
返回 void*
类型的设计使得函数可以支持链式调用。
2. memmove
void * memmove(void* dst, const void* src, size_t count)
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
- 如果源空间和目标空间出现重叠,就得使用memmove进行处理
-
复制的源区间是
[src, src + count)
-
复制的目标区间是
[dst, dst + count)
如何实现可以重叠:分情况讨论,一种就是没有重叠正常处理,另一种是重叠了进行特殊处理。
void *ret = dst; 保存目标内存区域的起始地址,用于返回
1. 检查内存区域是否重叠:
if (dst <= src || (char*)dst >= ((char*)src + count))
-
void*
指针之间可以直接进行比较(如==
,!=
,<
,<=
,>
,>=
),因为比较的是指针的地址值,而不是指针所指向的数据。所以dst <= src 可以不加char*。 -
如果涉及运算,必须将
void*
转换为具体类型的指针(如char*
)才能进行算术运算。 -
dst <= src
:如果目标内存区域的起始地址小于或等于源内存区域的起始地址,说明没有重叠,或者重叠部分不会影响复制。如下例子,不管count多大都不会影响复制。
src
|
a b c d e f g
|
dst
-
(char*)dst >= ((char*)src + count)
:如果目标内存区域的起始地址大于或等于源内存区域的结束地址(即src + count
),说明没有重叠。如下例,例如 count = 2,不影响复制。
src src+count
| |
a b c d e f g
|
dst
如果没有重叠或重叠不影响复制:
while (count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
2.如果重叠了并且会影响复制怎么办:
比如这种情况:dst > src 并且dst < src+count。
src src+count
| |
a b c d e f g
| |
dst dst+count
如果还和上面一样进行复制,c变成a,d变成b,e就变成a了,我们想要得复制结果是e是c,因为重叠影响了复制,导致内容被覆盖了。
需要从高地址到低地址逐字节复制,以避免覆盖源内存区域的数据。
如果我们找到dst的末尾位置,从末尾逐步向前复制就可以解决这个问题。
先把c赋给e,dst 和 src 各自减一,b赋给d,a赋给c,同样是进行count步。
void * memmove(void *dst, const void* src, size_t count)
{
void *ret = dst;
if(dst<=src || (char*)dst >= ((char*)src+count))
{
//低到高
while (count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1 ;
src = (char*)src + 1;
}
}
else
{
//把指针放到高处
dst = (char*)dst + count-1;
src = (char*)src + count-1;
//高到低
while (count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst - 1 ;
src = (char*)src - 1;
}
}
return ret;
}
3. strstr
用于在字符串 str1
中查找子字符串 str2
的第一次出现位置。如果找到,返回 str2
在 str1
中的起始地址;如果找不到,返回 NULL
。
const char* my_strstr(const char* str1, const char* str2)
{
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = str1;
-
str1
是主字符串,str2
是要查找的子字符串。 -
s1
和s2
是临时指针,用于在查找过程中遍历str1
和str2
。 -
cur
是当前查找的起始位置,初始化为str1
的起始地址
if (*str2 == '\0')//特殊场景
return str1;
- 如果
str2
是空字符串(即*str2 == '\0'
),则直接返回str1
,因为空字符串是任何字符串的子字符串。
while (*cur)
{
s1 = cur;
s2 = str2;
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return cur;
cur++;
}
循环遍历 str1
,直到 cur
指向的字符为 '\0'
(即字符串结束)。
如果cur++,说明从当前cur开始没找到str2,所以去下一个位置做开始进行寻找。每次循环开始时,s1
被设置为 cur
,s2
被设置为 str2
,准备开始新一轮的匹配
-
如果
*s1
和*s2
相等且都不为'\0'
,则继续比较下一个字符。 -
如果
*s1
或*s2
为'\0'
,或者字符不相等,则退出循环。 -
只有由于 str2 遍历完导致退出循环这种情况下,才说明str1中是有str2的,开始位置就是cur
-
如果*s1是'\0',说明str2还没走完,str1已经走完了,一定不符合
-
如果字符不相等,那cur去下个位置,s2回到str2开头重新寻找
-
最后如果str1遍历完都没能返回结果,那就说明不存在,返回NULL。
const char* my_strstr(const char* str1, const char* str2)
{
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = str1;
assert(str1 && str2);
if (*str2 == '\0')//特殊场景
return str1;
while (*cur)
{
s1 = cur;
s2 = str2;
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return cur;
cur++;
}
return NULL;
}
下面三个出现的相对较少:
1.strlen
int my_strlen1(const char* str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
int my_strlen2(const char* str)
{
//不能创建临时变量
if(*str == '\0')
return 0;
return 1 + my_strlen2(str+1);
}
2. strcpy
char *my_strcpy(char *dst, const char *src)
{
char *ret = dst;
assert(dst && src);
while((*dst++ == *src++))
;
return ret;
}
3. strcmp
int strcmp(const char *str1, const char *str2)
{
while(*str1 == *str2)
{
if(*str1 == '\0')
return 0;
str1++;
str2++;
}
if(*str1 > *str2)
return 1;
else
return -1;
}