目录
前言
小祥一共实现了九种常用的字符函数和字符串函数,像mem开头的都是与内存相关的库函数,也叫内存函数。
小祥我已经对各个库函数的模拟实现的做了尽可能多的注释与解析
希望可以对不清楚的人提供一些帮助,能帮到你们摆脱迷糊,若有帮助的话请不要忘了在评论区留言
当然小祥我在里面可能有会有一些错误或失误不足,若发现请各位大佬们指出指正,相互讨论共同进步
1.strlen(计算字符串长度的函数)
思路
要实现strlen函数首先需要知道字符串是以什么为结束的标志,我想有人会说是'\0',没错就是以'\0'为结束的标志,在strlen函数中返回的是字符串中'\0'前面出现的字符个数(注意不包含'\0'自己)。
既然要返回字符串中字符的个数,那函数的返回值类型是什么?有人可能会说是int类型,当然也没什么问题,返回字符串中字符的个数用整形int来返回似乎没什么毛病,但仔细想想字符的个数只能是一个大于等于0的正数那我们可以用一个无符号的整形size_t(Unsigned int)来接受这个值是不是更好呢(这样可以确保你写的函数返回的永远是一个正数,从而避免得出的字符个数是负数的尴尬局面)
最后函数的参数要指向的字符串必须要以'\0'结尾(否则结果是个随机值)
话不多说模拟实现开始
//方式一
//计数器
size_t my_strlen(const char* str)//const关键字修饰*str,*str内容无法修改(更加安全)
{
int count = 0;//计数
//str为字符串首元素的地址,*str对str(进行解析引用操作)得到str指向的首元素a
while (*str)//当*str='\0'时循环结束,'\0'的ASIIC 码值就是0,而0为假故循环结束
{
str++;//让str指向下一个元素的地址
count++;
}
return count;
}
//方式二
//不创建临时变量(本质是递归)
size_t my_strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
//方式三
//指针-指针的方式
size_t my_strlen(const char* s)
{
char* p = s;
while (*p != '\0')
p++;
return p - s;//指针减指针等于指针之间的元素个数 (a b c d )
}
//方式四
int test_strlen(const char* str)
{
const char* eos = str;
while (*eos++); //*eos++最终会向后偏移两个/0,*eos='\0'时又要执行eos++
return(eos - str-1);//结果指针eos-str最后多了个\0要减1,才是字符串的长度
}
int main()
{
char arr[] = "abcd";//字符串结尾处隐藏了一个'\0'
//char arr[]={'a','b','c','d','\0'};//计算字符数组的长度末尾要加'\0',否者计算的是个随机数
printf("%d", my_strlen(arr));
}
方法三指针-指针图示
2.strcpy
库函数strcpy的定义及解释
注意:库函数strcpy在把源字符内容,拷贝到目标数组里是通过指针的方式,指针是一个一个把指向的字符元素赋值给目标数组
这其中就需要确定指针指向的内存空间是多大,由于一个字符所占空间的大小是一个字节,故定义参数时定义的是char*的指针类型
可以看到库函数上char* source被const关键字修饰(目的为了保证安全性,防止source被修改)
const 关键字修改类型声明的类型或函数参数的类型,以防止值发生变化。
返回值类型是char*,由于数组内字符在内存中是依次存放,故最会只需返回目标的首地址就行了(关于内存的存储方式在此不过多的解释,以后可能会专门解释)
模拟实现strcpy
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);//assert断言 防止传过来得为空指针
while(*dest++ = *src++)//*src='\0'时把'\0'赋给了*dest然后跳出循环
{
;
}
//上面为下面的简写
/*assert(dest != NULL);
assert(src != NULL);*/
//while (*dest = *src)
//{
// dest++;
// src++;
//}
return ret;
}
int main()
{
char arr[10] = "abcdef";
char arr1[10] = {0};
my_strcpy(arr1,arr);
}
3.strcat(功能在目标字符串末尾追加字符串)
库函数strcat的定义及解释
模拟实现strcat
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;//记住首地址dest
assert(dest != NULL);
assert(src != NULL);
while(*dest)//先让dest指向'\0'
{
dest++;
}
while(*dest++ = *src++)//再在'\0'位置追加字符串
{
;
}
return ret;
}
int main()
{
char arr[10] ="abcd";
char arr1[] = "efg";
my_strcat(arr,arr1);
}
注意:指明目标空间的大小,让目标空间可以放下source内的字符串,下面为未指定目标空间大小导致栈溢出的错误演示
4.strstr
库函数strstr的定义及解释
模拟实现strstr
#include <assert.h>
char* my_strstr(const char* str1,const char* str2)
{
assert(str1,str2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = str1;
if (*str2 == '\0')
{
return (char*)str1;
}
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 && *s2 && (*s1 == *s2))//*s1和*s2不为NULL,且*s1==*s2三个条件缺一不可
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cp;
}
cp++;
}
return NULL;//若没有找到字符串则返回会空指针
}
int main()
{
char arr1[] = "abcbcdef";
char arr2[] = "bcd";
char* ret = my_strstr(arr1,arr2);
if (ret == NULL)
{
printf("没找到了");
}
else
{
printf("找到了:%s",ret);
}
}
strstr不重叠内存拷贝图解(可以与提供代码对照)
5.strcmp
库函数strstr的定义及解释
模拟实现strcmp
int my_strcmp(char* a, char* b)
{
assert(a && b);
while (*a==*b)
{
if (*a == '\0')
{
return 0;//'\0'为结束标志
}
a++;
b++;
}
return *a - *b;//返回的结果为俩个字符的ASIIC码值相减
}
int main()
{
char p[20] = "abcd";
char q[20] = "abad";
int ret=my_strcmp(p,q);
if (ret == 0)
{
printf("p==q");
}
else if(ret>0)
{
printf("p>q");
}
else
{
printf("p<q");
}
}
6.memcpy- 内存拷贝(函数应该拷贝不重叠的内存)
库函数strstr的定义及解释
注意:memcpy可以给任意类型的数据进行拷贝操作,故要实现它需要用void*(无具体类型的指针,可以接受任意类型的数据,可用性更强),这也就导致需要指明拷贝内容的内存大小占多少个字节num作为第三个参数
对于重叠内存块部分标准strstr的库函数是不好实现的,有memmov这个库函数来实现,但vs里的strstr的库函数实现了和memmove一样的功能
memcpy : vs的库函数实现了内存重叠拷贝,其他编译器不一定实现了,标准只需实现不重叠内存的拷贝
模拟实现memcpy
void* my_memcpy(void* dest,const void* src,size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;//强转为char*类型,由于char类型大小为一个字节
dest = (char*)dest + 1;
src = (char*)src + 1;
//*(((char*)dest)++) = *(((char*)src)++);
//()分隔可以实现上面一样的功能在vs编译器下,其他编译器可能不行(不推荐使用)
//*(char*)dest++ = *(char*)src++;//err
//1.由于强制类型转换为一种临时的状态,++为后置的,
//当++时强制类型的转换的状态已经过去了,依旧是void*
//2.也可能是后置++的优先级要高于强制类型装换
//上述为个人理解若有不对请大佬们在评论指出!!!!!
}
return ret;
}
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
int arr2[20] = { 0 };
my_memcpy(arr2,arr1,16);
}
7.memmove- 内存拷贝(函数可以拷贝重叠的内存)
与标准memcmp库函数类似也是实现内存的拷贝,但它可以处理内存重叠的情况
模拟实现memmove
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while(num--)
{
*((char*)dest + num) = (*(char*)src + num);
}
}
}
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
int arr2[20] = { 0 };
my_memcpy(arr2,arr1,16);
//内存重叠拷贝
my_memcpy(arr1+2,arr1,16);
my_memcpy(arr1,arr1+2,16);
}
重叠内存拷贝图解
8.memcmp- 比较两个内存块
库函数strstr的定义及解释
模拟实现memcmp
//memcmp - 内存比较
int my_memcmp(const void* str1,const void* str2,int num)
{
//方式一
//while (*(((char*)str1)++)== *(((char*)str2)++))
//{
// num--;
// if (num == 0)
// {
// return 0;
// }
//}
//return *((char*)str1-1) - *((char*)str2-1);
//这里减1是因为while()内是后置++,当跳出循环时会加1,导致结果后偏移1个字节
//方式二
while (*(char*)str1==*(char*)str2)
{
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
num--;
if (num == 0)
return 0;
}
return *(char*)str1 - *(char*)str2;
}
int main()
{
int arr1[] = {1,8,3,4,5,6};
int arr2[] = {9,8,4,5,6};
int ret =my_memcmp(arr1, arr2,8);
//arr1>arr2返回>0的数,arr1<arr2返回<0的数,arr1=arr2返回0
printf("%d\n",ret);
return 0;
}
9.memset(填充内存块)
第一个参数:指向要填充的内存块的指针。
第二个参数:要设置的值。该值作为 int 传递,但该函数使用此值的无符号字符转换填充内存块。
第三个参数:要填充的的字节数,size_t(unsigned int)类型
模拟实现memset
//memset - 内存设置
void* my_memset(void* dest,int value,size_t num)
{
assert(dest);//断言
void* ret = dest;
while (num--)
{
*(char*)dest = value;//给每个字节的内存赋值
dest = (char*)dest + 1;
}
return ret;
}
int main()
{
int arr[10] = { 0 };
my_memset(arr, 1, 20);//以字节为单位设置内存
for (int i = 0; i < 10; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
小白提示
下图为内存调试时如何显示内存里值被改为1(右键arr以16进制显示及可看到20个字节都被改为1)