导语:
说到字符串与内存函数,大部分人对前者比较熟悉,然而后者出现的频率也比较高。本次就挑选几个常用的函数进行模拟实现、代码分析。
正文:
strlen函数的模拟实现
首先请浏览strlen官方定义
int my_strlen(char* arr)
//{
// int count = 0;
// while (*arr)
// {
// count++;
// arr++;
// }
// return count;
//}
//int main()
//{
// char arr[] = {"abcdef"};
// int ret = my_strlen(arr);
// printf("%d\n",ret);
// return 0;
//}
代码逻辑:
strlen函数就是求字符串的长度,求长度就是要确定两点:起点和终点。起点我们由数组名对应的地址确定,那终点呢?因为是字符串,所以'\0'是他们的终点。由此可知,我们只需要遍历字符串直到找到'\0',就能求出字符串的长度啦。
strcpy函数的模拟实现
首先请浏览strcpy官方定义
//模拟实现strcpy
//char* my_strcpy(char* arr1, char* arr2)
//{
// assert(arr2);
// char* begin = arr1;
// while (*arr1++ = *arr2++)
// {
// ;
// }
// return begin;
//}
//int main()
//{
// char arr1[] = { "abcdef" };
// char arr2[] = { "hij" };
// char* ret = my_strcpy(arr1,arr2);
// printf("%s",ret);
// return 0;
//}
代码逻辑:
strcpy就是把目标字符串给拷贝过来,在拷贝的过程中,会把原来的字符给覆盖。
看到代码,功能函数中,因为会对数组解引用所以要提前判断是否为空,这里我只判断了arr2其实两个都判是最好的。
因为最后要返回地址,所以提前保存好arr1的地址。可能有人会问,为什么不能直接在函数结束的时候返回arr1呢?这是因为此时的arr1指向的地址已经改变,所以如果直接返回,将会造成数据打印不全的后果。
然后while循环中先赋值再后置加加,能巧妙地化解代码,赏心悦目。
strcmp函数的模拟实现
首先请浏览strcmp官方定义
//模拟实现strcmp
//int my_strcmp(char* arr1, char* arr2)
//{
// while (*arr1 == *arr2)
// {
// if (*arr1 == '/0')
// return 0;
// arr1++;
// arr2++;
// }
// return (*arr1 - *arr2);
//}
//int main()
//{
// char arr1[] = {"abcdef"};
// char arr2[] = {"abcef"};
// int ret = my_strcmp(arr1,arr2);
// if (ret > 0)
// {
// printf("大于");
// }
// else
// printf("小于等于");
// return 0;
//}
代码逻辑:
strcmp就是比较字符串大小的函数,具体是怎么比的,这里不再一一赘述,官方定义里面有详细的解答。现在来看到代码部分
注意如果走到循环中的if语句里,说明两个字符串从头到'\0'全都相等,那么他们就一定相等。
倘若跳出循环了,不用再次比较谁大谁小,直接返回二者的差值。这是因为定义规定:若大于,则返回正值;若小于,则返回负值。那就直接解引用做差喽。这样的代码简洁而流畅。
strcat函数的模拟实现
首先请浏览strcat官方定义
// 模拟实现strcat
//char* my_strcat(char* arr1, const char* arr2)
//{
// assert(arr2);
// char* begin = arr1;
// while (*arr1)
// {
// arr1++;
// }
// while (*arr2)
// {
// *arr1 = *arr2;
// arr1++;
// arr2++;
// }
// return begin;
//}
//int main()
//{
// char arr1[20] = { "abcdef" };
// char arr2[] = {"hij"};
// char* ret = my_strcat(arr1,arr2);
// printf("%s",ret);
// return 0;
//}
代码逻辑:
strcat就是连接字符串的函数,同样的关于定义我们不讲,直接看到代码部分
首先你要找到arr1的终点,通过遍历找到终点
接着把目标字符串一个个赋给源函数。这里有人也许会问,arr1不是已经到终点了吗,怎么还++呢,这样不会造成野指针的问题吗?其实arr1确实已经到终点了,但是我们只是把arr2的值往后赋,并没有使用arr1,所以不用担心!
由于arr1指向的位置已经发生改变,所以我们要一开始就保存他的起始位置。
strstr函数的模拟实现
首先请浏览strstr官方定义
//模拟实现strstr
char* my_strstr(char* arr1, char* arr2)
{
char* str1 = arr1;
char* str2 = arr2;
while (*str1)
{
char* front1 = str1;
char* front2 = str2;
while (*front1 == *front2)
{
front1++;
front2++;
if (*front2 == '\0')
{
return str1;
}
}
str1++;
}
}
int main()
{
char arr1[] = {"abcdefhij"};
char arr2[] = {"def"};
char* ret = my_strstr(arr1, arr2);
printf("%s\n", ret);
return 0;
}
代码逻辑:
strstr就是字符串中找子字符串,直接看代码
这个代码的难点在于画好图,分清指针。
首先明确,以str1为目标,找到等同于str2的字符串。那么如果str1都找到终点了还没有找到,那么我们可以确定找不到子字符串,此乃while里面是str1的意义
来到循环内部:若子字符串存在,则对应的字符一一相等。为此我们需要两个可以移动的指针来判断,这就是为什么设置了两个front如。若front相等则++,直到进入if语句。此时则证明存在一个子字符串。那么返回此时的str1的地址即可
若跳出循环,则说明不匹配,那么移动str1重新配对。
memmove函数的模拟实现
首先请浏览memmove官方定义
//void* my_memmove(void* dest, const void* src, size_t num)
//{
// assert(dest&&src);
// void* ret = dest;
// if (dest < src)
// {
// while (num--)
// {
// *(char*)dest = *(char*)src;
// src = (char*)src + 1;
// dest = (char*)dest + 1;
// }
// }
// else
// {
// while (num--)
// {
// *((char*)dest + num) = *((char*)src + num);
// }
// }
// return ret;
//}
//int main()
//{
// int arr1[20] = { 1,2,3,4,5,6,7,8,9,10 };
// my_memmove(arr1 + 4, arr1 + 2, 20);
// for (int i = 0; i < 10;i++)
// {
// printf("%d ", arr1[i]);
// }
// return 0;
//}
代码逻辑:
memmove就是移动内存的函数,他的适用性更强,可以满足不同类型的数据
代码的关键在于画图
移动涉及了两个位置:dest /src,由于我的移动是要把前一个位置的数据覆盖到下一个位置,那么我们就必须考虑到倘若两个位置有交叉时会不会出现数据遗漏的问题(这里说的比较抽象,我们画图来看看
上述图是src<dest的情况,假如src:12345,dest:34567,现在就是要把34567变成12345,使得最后数组变成12 12345 8910
那么我们该怎么做呢?
有人会说,简单,我把src中的1给dest的3,src中的2给dest的4,src中的3给dest的5,以此类推。咋一看还挺好,但是仔细一想,倘若我要是把src中的1给dest的3,那么到src中的3给dest的5时我会发现此时的3 已经不是3了而是1了,原来的3已经被覆盖了!3找不回来了。
那该怎么解决呢?其实我们换一种思维就行了,刚刚我们是从前往后覆盖,那我们换用从后往前覆盖可不可以呢?
现在,把src中的5给dest的7,把src中的4给dest的6,把src中的3给dest的5,以此类推。这样我们就解决了刚才的难题。
上述图是dest<src的情况,例如dest:12345,src:34567,希望数组最后变成34567 678910
有了上述的经验我们不难发现直接将src中的值赋给dest就行,可以看到这是直接由前到后的覆盖,与第一个例子截然相反。
有了2种不同情况,我们可以做一个归纳:
若dest<src,则从前往后覆盖;若dest>src,则从后往前覆盖。
相等的情况则二者都可
看懂了这些,那么你差不多也看懂代码了。不过还有几点要注意的地方
1 因为memmove适用性广,所以我们并不知道他的具体的返回类型,因此用void*
2 因为移动的单位(也就是num的单位)是比特,所以我们在进行指针移动时要强制类型转换成char*,这是最小单位,能满足不同的条件。
3 强制类型转换只能的生命只有一次,因此要重复强转
结语:
通过这些函数的练习,我们可以发现关于指针的使用一定要谨慎,传参和解引用要多加小心。此外,在遇到一些题目有几个指针的时候,画逻辑图,可以极大的帮助我们理清思路、提高准确率。最后,我们在完成代码之后,是不是还可以改进代码,让其更加可读、简洁,让其美如画!
以上就是今天的内容,关于字符串和内存的函数还有很多,这里仅仅列举了常用的几个,读者若有兴趣可以自行尝试其余的函数。
我是happy sky,编程路上你我一起探索。