目录
前言
c语言定义在<string.h>的函数有很多种类(空终止字节字符串 - cppreference.com)我们今天模拟实现以下几种:
1.字符串操作:strncpy,strncat
2.字符串检验:strlen,strstr
3.字符数组操作:memcpy,memmove
1. 字符串操作
1.1 strncpy
1.1.1 讲解
C 库函数 char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest。 n表示最多复制的字符。最后会返回desk的地址。举个例子:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "never give up";
char arr2[50] = "";
printf("%s\n", strncpy(arr2, arr1,7));
return 0;
}
输出结果:never g
我们想将arr1中的数据全部“传输”到arr2中,一般会想到利用for 循环将arr1中的一个元素一个一个拷进去,但其实也可以用strncpy一步到位,7(n),表示复制的字符数。这时,有些小伙伴可能就要问了:如果你要求的字符数大于arr1会怎样?答:不会怎样,\0传进去了,后面传再多也没用了。
1.1.1 模拟实现
首先,凭借着参数及返回值的讲解,我们的函数声明首先就上来了。
char* my_strncpy(char* desk, const char* src, size_t num) ;
有特殊到一般,传了空指针没法处理,返回NULL,正常传进来,处理,也当然会很自然的想到循环。循环num次,一个一个拷贝。
// 模拟实现strncpy
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
char* my_strncpy(char* desk, const char* src, size_t num)
{
if (!desk || !src)
return NULL;
char* ret = desk;
while (num--)
{
*desk++ = *src++;
}
return ret;
}
int main()
{
char arr1[] = "never give up";
char arr2[50] = "";
printf("%s\n", my_strncpy(arr2, arr1,strlen(arr1)+1));
return 0;
}
该段代码简练的一点是使用了后置++,减少了代码量。注意要返回原来的地址,所以还要再创建一个指针变量储存,最后好返回。
1.2 strncat
1.2.1 讲解
C 库函数 char *strncat(char *dest, const char *src, size_t n) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。举个例子:
#define _CRT_SECURE_NO_WARINGS 1
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[49] = "never ";
char arr2[] = "give up";
printf("%s\n", strncat(arr1, arr2, 6));
return 0;
}
输出结果:never give u
就好像是把arr1的从\0开始替换成了arr2,前六个字符一样。
1.2.2 模拟实现
你可以这么想象,arr1\0前面是一个数组,从\0开始后面又是一个数组,strncat相当于把arr2中的内容strcpy到了那个后面的数组一样。
模拟实现strncat
//
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
char* my_strncat(char* desk, const char* src, size_t num)
{
if (!desk || !src)
return NULL;
char* ret = desk;
while (*desk++)
{
;
}
desk--;
while (num--)
{
*desk++ = *src++;
}
return ret;
}
int main()
{
char arr1[49] = "never ";
char arr2[] = "give up";
printf("%s\n", my_strncat(arr1, arr2, 6));
return 0;
}
所以,我们先就是找到了\0的地址,其余跟strncpy简直一模一样!
2. 字符串检验
2.1 strlen
2.1.1 讲解
相信大家对strlen函数都不陌生了吧,就是返回\0之前的字符数。
2.1.2 模拟实现
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* arr)
{
assert(arr);
const char* start = arr;
while (*arr++)
{
;
}
return arr - start;
}
int main()
{
char arr[] = "never give up";
printf("%zd\n", my_strlen(arr));
return 0;
}
常见的计数器实现就不过多介绍了,以上便是指针 - 指针的形式。
2.2 strstr
2.2.1 讲解
在wps办公中,我们常有一个查找和替换,strstr就是一个类似于查找功能的函数。char * strstr ( const char * str1, const char * str2); 找的就是str2在str1中首次出现的位置。注意不单检测str1中有没有str2,没有返回空指针,有返回首次出现的地址
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abbcdefghij";
char arr2[] = "bcdef";
printf("%s\n", strstr(arr1, arr2));
return 0;
}
输出:bcdefghij
2.2.2 模拟实现
我相信大家开始的思路应该都是在arr1中开始遍历,先找到与arr2第一个字符一样的字符再说,然后看看第二个是否相等,不相等找到的不是,下一个;相等,看看第三个……
由于我们相当于是检验找到前那个元素是否满足要求,肯定就涉及到了while循环。确认第一个字符对不对跟确认第二个,第三个对不对是不是又循环了?你地址在那里变换,如果不是,是不是该储存一个变量来储存,好回滚?
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
// 函数返回substr在str中第一次出现时的地址
char* my_strstr(const char* str, const char* substr)
{
assert(str);
assert(substr);
char* temp1 = NULL;
char* temp2 = NULL;
int flag = 0;
while (*substr && *str)
{
if (*str++ != *substr)
{
if (flag)
{
str = ++temp1;
substr = temp2;
flag = 0;
continue;
}
temp1 = str;
temp2 = substr;
}
else
{
substr++;
flag = 1;
}
}
if (!flag || *substr)
return NULL;
return temp1;
}
int main()
{
char arr1[] = "abbiijkt";
char arr2[] = "ijkt";
char* ret = my_strstr(arr1, arr2);
printf("%s\n", arr1);
if (ret != NULL)
printf("%s\n", ret);
else
printf("no find\n");
return 0;
}
或者说,也可以这么理解,有2个指针跟str有关,有2个指针跟str2有关,其中一种指针的作用是找到并保存str中某个字符跟substr中某个字符相等的地址,另一种指针就看第二个,第三个……是否跟substr2一样,直到\0都一样就返回str前一种指针了,不一样,str前一种指针加1就行了。两个后一个指针回到该回的地方。
3. 字符数组操作
3.1 memcpy
3.1.1 讲解
它跟strncpy很像,都是针对内存进行操作的,但strncpy干不了的,memcpy可以干,因为memcpy可以接收任意类型的指针。C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1。举个例子:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr1[49] = "";
char arr2[] = "never give up";
printf("%s\n", (char*)memcpy(arr1, arr2 + 1,sizeof(char) * 12));
int arr3[10] = { 0 };
int arr4[] = { 1,2,3,4,5,6,7,8,9 };
memcpy(arr3, arr4, sizeof(int) * 6);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
return 0;
}
输出结果:
ever give up
1 2 3 4 5 6 0 0 0 0
这里要注意的是由于memcpy接收的是任意类型的数据,所以也无法判定是什么数据类型,有多少个元素,传过去的是字节数,不是字符数
3.1.2 模拟实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* desk, const void* src, size_t num)
{
if (!desk || !src)
return NULL;
void* ret = desk;
while (num--)
{
*((char*)desk)++ = *((char*)src)++;
}
return ret;
}
int main()
{
char arr1[49] = "";
char arr2[] = "never give up";
printf("%s\n", (char*)my_memcpy(arr1, arr2 + 1,sizeof(char) * 12));
int arr3[10] = { 0 };
int arr4[] = { 1,2,3,4,5,6,7,8,9 };
my_memcpy(arr3, arr4, sizeof(int) * 6);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
return 0;
}
其实,不必因为她接收的是无类型的指针就觉得怎样怎样,就多了一个强制类型转换,如此而已。
3.2. memmove
3.2.1 讲解
其实这个函数跟memmove又是很像,memcpy能干的,memmove能干,memcpy可能干不了的(取决于编译器),memmove能干,memmove多出来的一个功能便是当arr1与arr2有重叠部分时,仍然可以很好的处理。由于在vs2022下memcpy已经设计得跟memmove差不多了,就只演示memmove的了
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
int main()
{
char arr[] = "abbcdefg";
printf("%s\n", arr);
printf("%s\n", (char*)memmove(arr + 2, arr, 5));
return 0;
}
3.2.2 模拟实现
这里主要是不同情况拷贝顺序的问题,这么做是为了防止 src中要拷贝给desk中的数因为重叠而被篡改,其他就都跟memcpy一样了。除此以外,从后往前拷,多面的地址通过指针运算就可以得到了,不要陷进去了。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memmove(void* desk, void* src, size_t num)
{
if (!desk || !src)
{
return NULL;
}
void* ret = desk;
if (desk <= src)
{
while (num--)
{
*((char*)desk)++ = *((char*)src)++;
}
}
else
{
while (num--)
{
*(((char*)desk) + num) = *(((char*)src) + num);
}
}
return ret;
}
int main()
{
char arr[] = "abbcdefg";
printf("%s\n", arr);
printf("%s\n", (char*)my_memmove(arr + 2, arr, 5));
return 0;
}
好了,今天字符串函数和内存函数的模拟实现就分享到这里了。
感谢各位大佬的支持和指正