strlen
函数原型:size_t strlen(const char* str),计算字符串长度的函数,不包含'\0',注意返回值为size_t。
strlen与sizeof的区别:1.strlen是函数,sizeof是关键字。2.strlen传入的参数是const char*,因此用于计算字符串的长度,而sizeof可以是类型,且sizeof数组时数组名代表整个数组。3.strlen遇到'\0'结束,sizeof计算一个数据类型占用内存的大小。
模拟实现:
size_t my_strlen(const char* str)
{
assert(str);
size_t ret = 0;
while (str[ret] != '\0')
ret++;
return ret;
}
2. strcpy
** 函数原型**:char* strcpy(char* des,const char* src),将一个字符串复制到des开始的内存空间中。
src必须以'\0'结尾,复制的时候会将'\0'也复制到des中,des空间必须能容纳复制后的字符串,des必须是可改变的内存空间。
如果复制超出des空间的字符串,程序会崩溃并报错堆栈空间被破坏,即缓冲区溢出。
模拟实现strcpy时还要注意内存重叠问题,如果des所在的地址比src的地址大,且des地址比src的'\0'处地址还要小,那么此时如果按照从低到高的地址进行赋值,则会导致前面赋的值改变了后面的值,结果就是无法复制正确的结果。过程如图:
模拟实现:
//不考虑内存重叠
char* my_strcpy(char* des, const char* src)
{
assert(des != NULL && src != NULL);
char* ret = des;
while ((*des++ = *src++) != '\0');
return ret;
}
//考虑内存重叠
char* mem_strcpy(char* des, const char* src,size_t count)
{
assert(des != NULL && src != NULL);
char* ret = des;
if (des > src && des < src + count)
{
des = des + count - 1;
src = src + count - 1;
while (count--)
*des-- = *src--;
}
else
{
while (count--)
*des++ = *src++;
}
return ret;
}
3. strcat
函数原型:char* strcat(char* des,const char* src),简单来说是向des后插入字符串,同样的,源字符串必须以'\0'结尾,且des是可改变的内存,且空间足够,否则运行时报错堆栈溢出。
在模拟实现时要注意如果用strcat自己向自己后面插入的问题,如果只是单纯的将des向后移动到'\0'的位置,然后用src的内容依次给des赋值,带来的问题是会将'\0'覆盖,且由于src也是这个字符串,所以'\0'已经无法获得了,我的解决方式是如果自己向自己后面插入,则从高位向地位赋值。
模拟实现:
char* my_strcat(char* des, const char* src)
{
assert(des != NULL && src != NULL);
char* tmp = des;
while (*des)
des++;
if (tmp == src)
{
size_t n = strlen(src) + 1;
des = des + strlen(src);
src = src + strlen(src);
while (n)
{
*des-- = *src--;
n--;
}
return tmp;
}
while ((*des++ = *src++) != '\0');
return tmp;
}
4. strcmp
函数原型:int strcmp(const char* str1,const char* str2),比较两个字符串的大小,如果str1大,则返回大于0的数,str1小则返回小于0的数,相等则返回0,比较规则为一个字符一个字符的按照ASCII码大小比较,注意:小写字母要比大写字母的ASCII码大。优先比较字符大小,字符都相等则比较长度。
模拟实现:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 != NULL && str2 != NULL);
while (*str1!='\0' && *str1 == *str2)
{
str1++;
str2++;
}
if (*(unsigned char*)str1 > *(unsigned char*)str2)
return 1;
else if (*(unsigned char*)str1 < *(unsigned char*)str2)
return -1;
else
return 0;
}
5. strncpy
函数原型:char* strncpy(char* des,const char* src,size_t n),同样是拷贝字符串,但是可以指定长度,如果n大于src长度,则在多余的空间后赋值'\0'。
模拟实现:
char* my_strncpy(char* des, const char* src, size_t n)
{
assert(des != NULL && src != NULL);
char* ret = des;
while (n && (*des++=*src++)!='\0')
n--;
while (n)
{
*des++ = '\0';
n--;
}
return ret;
}
6. strncat
函数原型:char* strncat(char* des,const char* src,size_t n),拷贝字符串到字符串的末尾,与strcat不同的是可以自己给定要拷贝多少字节。
注意:n大于src的长度的话,多余的也要填充'\0',且这个函数同样需要考虑给末尾添加自己的情况。
模拟实现:
char* my_strncat(char* des, const char* src, size_t n)
{
assert(des != NULL && src != NULL);
char* ret = des;
while (*des)
des++;
//在自己后面添加自己的情况
if (ret == src)
{
if (n > strlen(ret))
{
char* des_tmp = des;
des_tmp += strlen(ret);
size_t count = n - strlen(ret);
while (count--)
*des_tmp++ = '\0';
}
size_t num = strlen(ret);
while (num--)
*des++ = *src++;
return ret;
}
while (n && (*des++ = *src++) != '\0')
n--;
while (n)
{
*des++ = '\0';
n--;
}
if (*(--src) != '\0')
*des = '\0';
return ret;
}
7. strstr
函数原型:char* strstr(const char* str1,const char* str2),查找一个字符串在另一个字符串中的位置,如果不存在则返回NULL。
此函数可用KMP算法来写。
模拟实现
char* my_strstr(const char* str1, const char* str2)
{
char* cp = (char*)str1;
char* sub = (char*)str2;
char* cpcur = NULL;
while (*cp)
{
cpcur = cp;
sub = (char*)str2;
while (*cpcur && *sub && *cpcur == *sub)
{
cpcur++;
sub++;
if (*sub == '\0')
return cpcur;
}
cp++;
}
return NULL;
}
8. memcpy
函数原型:void memcpy(void des,const void src,size_t n),按照字节进行拷贝,因为形参类型是void,因此可以传入任意类型的指针,但是在拷贝时是按照一个字节一个字节进行拷贝的。
此函数遇到'\0'并不会停下,因为并不是字符串拷贝函数。
此函数同样需要考虑内存重叠问题,但是源码并没有实现,而是用了另一个函数memmove。
模拟实现:
//不考虑内存重叠
void* my_memcpy1(void* des,const void* src, size_t n)
{
assert(des != NULL && src != NULL);
void* ret = des;
while (n--)
{
*(char*)des = *(char*)src;
des = (char*)des + 1;
src = (char*)src + 1;
}
return ret;
}
//考虑内存重叠
void* my_memcpy2(void* des, const void* src, size_t n)
{
assert(des != NULL && src != NULL);
char* des_tmp = (char*)des;
char* src_tmp = (char*)src;
if (des_tmp > src_tmp && des_tmp < src_tmp + n)
{
des_tmp = des_tmp + n - 1;
src_tmp = src_tmp + n-1;
while (n--)
*des_tmp-- = *src_tmp--;
}
else
{
while (n--)
*des_tmp++ = *src_tmp++;
}
return des;
}
9. memmove
函数原型:void* memmove(void* des,const void* src,size_t n),同样是内存拷贝,但是考虑了内存重叠问题。
模拟实现:
void* my_memmove(void* des, const void* src, size_t n)
{
assert(des != NULL && src != NULL);
char* des_tmp = (char*)des;
char* src_tmp = (char*)src;
if (des_tmp > src_tmp && des_tmp < src_tmp + n)
{
des_tmp = des_tmp + n - 1;
src_tmp = src_tmp + n - 1;
while (n--)
*des_tmp-- = *src_tmp--;
}
else
{
while (n--)
*des_tmp++ = *src_tmp++;
}
return des;
}
10. memset
**函数原型:void memset(void str,int c,size_t n),将内存空间str n个字节设置成c,虽然传入的是int型,但是存储时是以无符号字符型存储。
模拟实现:
//c:以int传入,但是在填充内存时是以该值的无符号char填写。
void* my_memset(void* str, int c, size_t n)
{
assert(str != NULL);
char* str_tmp = (char*)str;
while (n--)
*str_tmp++ = (char)c;
return str;
}
11. memcmp
**函数原型:int memcmp(const void str1,const void str2),按字节进行比较大小,比较原理同strcmp。
模拟实现:
int my_memcmp(const void* str1, const void* str2,size_t n)
{
assert(str1 != NULL && str2 != NULL);
unsigned char* str1_tmp = (unsigned char*)str1;
unsigned char* str2_tmp = (unsigned char*)str2;
while (n && *str1_tmp == *str2_tmp)
{
n--;
str2_tmp++;
str1_tmp++;
}
if (n > 0)
{
if (*str1_tmp > *str2_tmp)
return 1;
else
return -1;
}
return 0;
}
总结:
例如strcpy函数,原型是char* strcpy(char* des,const char* src),在使用这个函数时我们可能直接这样,char str1[20]="hello",const char* str2="world",strcpy(str1,str2),导致一种错觉,那就是这好像是个字符串替换函数,然后des必须从字符串开头开始一样,实际上也可以这样strcpy(str1+2,str2+1)。这样看来,实际上des和src不过是标记一段内存的开始位置罢了,我想用内存a这个位置开始的内存中的内容来替换内存b位置开始的内存中的内容。这样在理解内存重叠问题时更加清晰。