请点下面链接:
-> 给大家推荐一款很火爆的刷题、面试求职网站 <-
前言
写过C语言的朋友相信都使用过strlen函数计算字符串长度,strcmp判断过字符串是否相等........而这些函数所需的头文件就是<string.h>,那你是否好奇过里面的函数是怎样实现的呢,要是将来有人想让你模拟实现一下strlen这个函数,你是否能写出呢?
这次将给各位带来<string.h>里的一些字符串与内存相关函数的模拟实现,但并非跟C语言库里一模一样,只是一种参考实现!!!
文章目录
正文
---字符串相关函数---函数介绍
size_t strlen ( const char * str );
用于 计算字符串长度,返回一个size_t的数字
size_t就是unsigned int 无符号整型的别名(因为长度不可能是负数)
==易错点==:
- 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
- 参数指向的字符串必须要以 '\0' 结束。
- 注意函数的返回值为size_t,是无符号的
模拟实现
strlen函数的模拟实现主要是捉住字符串是以 ' \0 ' 为结束标志,进行字符串的长度。
这边我给出三种模拟实现的方式:
方式一:计数方式(最容易想到的)
#include<assert.h>
//计数器方式
int my_strlen(const char * str)
{
assert(str);//断言,用于判断str是否为空,为空就提示报错并结束程序
//(可以提高代码的质量)头文件为<assert.h>
int count = 0;
while(*str++)
{
count++;
}
return count;
}
方式二:递归(不用创建临时变量)
//不能创建临时变量计数器
int my_strlen(const char* str)
{
if (*str == '\0')
return 0;
return 1 + my_strlen(str + 1);
}
方式三:(指针-指针的方式)
//指针-指针的方式
#include<assert.h>
int my_strlen(const char* str)
{
assert(str);
char* end = (char*)str;
while (*end!='\0')//先找到尾
{
end++;
};
return end - str;//用尾指针地址-起始地址=中间的个数
}
函数介绍
int strcmp ( const char * str1, const char * str2 );
strcmp最常用的用法就是判断一个字符串是否相等,
比如
但是这个函数其实是比较两个字符串大小的,只是他们对于的字符串如果相等那么就刚好返回零
它的规则是这样的:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
这里是因为数组a中的第三个字符c比数组b中的第三个字符f的ascll要小,所以返回一个小于零的数
模拟实现
其实就是对每一个字符进行一一比较,本质上是字符对应的ASCLL码进行比较大小!
#include<assert.h>
int my_strcmp(const char* src, const char* des)
{
assert(src && des);
while (*src == *des)
{
if (*src == '\0')//两个同时遇到\0,则表示它们字符串内容相等
return 0;
src++;
des++;
}
return *src - *des;//程序到这里,说明它们已经开始不相等了,则对应的字符相减即可
}
函数介绍
char* strcpy(char * destination, const char * source );//
用来进行字符串拷贝
比如:把arr1的内容拷贝到arr2中
==易错点==:
- 源字符串必须以 '\0' 结束。
- 会将源字符串中的 '\0' 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变(意思就是不能是常量字符串和const修饰的字符串)
模拟实现
方式一:(容易理解)
#include<assert.h>
char* my_strcpy(char* des, const char* src)
{
assert(des && src);
char* ret = des;//先保存目标地址的起始位置
while (*src!='\0')//进行拷贝
{
*des = *src;
des++;
src++;
}
*des = '\0';//把\0补上
return ret;
}
方式二:(帅就完事了)
#include<assert.h>
char* my_strcpy( char* des ,const char* src)
{
assert(des && src);
char* ret = des;//先保存目标地址的起始位置
while (*des++ = *src++)//进行拷贝(这种方式直接就把\0拷贝进去了)
{
;
}
return ret;
}
函数介绍
char * strcat ( char * destination, const char * source );
用于字符串追加
比如:在arr1后面追加arr2的“world!”,
==易错点==:
- 源字符串必须以 '\0' 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。(意思就是不能是常量字符串和const修饰的字符串)
- 不能自己给自己追加,否则会死循环。(比如不能strcat(arr1,arr1))
模拟实现
本质就是先找到目标字符串的尾地址,也就是\0的地址,然后从\0位置开始进行拷贝即可
#include<assert.h>
char* my_strcat(char* des, const char* src)
{
assert(des && src);
char* ret = des;//记录目标字符串的起始位置
char* end = des;//用于记录目标字符串的尾地址
while (*end)//找到尾地址
{
end++;
}
while (*end++ = *src++)//开始追加(到这里就是strcpy)
{
;
}
return ret;
}
函数介绍
char * strstr ( const char *str1, const char * str2);
其实就是字符串查找,从一个目标字符串中查找它的子串
比如:
==易错点==:
- 如果找到了对应的子串,则返回该目标字符串中开始匹配的起始位置的地址。
- 找不了则返回NULL
模拟实现
方法一:暴力美学
(暴力搜索,也是传说中的BF算法)
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* s1 = (char*)str1;
char* s2 = (char*)str2;
char* p1 = (char*)str1;//用来找回已经比较过的字符的下一个
while (*s1)//确保s1没有到尾
{
while (*s1 == *s2 && *s1!='\0' && *s2!='\0')//相等且两个不为零的情况下则继续往下比较
{
s1++;
s2++;
}
if (*s2 == '\0')//如果s2已经到尾了,则说明前面的都与s1相等,证明s1存在该子串
return p1;
p1++;//往后从str1的第二个字符开始比较
s1 = p1;
s2 = (char*)str2;//s2回归到第一个字符
}
return NULL;
}
方法二:(KMP算法)
(学过数据结构的应该都认识)这个实现起来有点复杂,难理解的可以先稍作了解,只是单纯想让你们知道有这么一个算法!
#include<assert.h>
void Getnext(int* next, char* mstring, int len)//next数组获取
{
assert(len >= 2);
next[0] = -1;
next[1] = 0;
int i = 2;
int k = 0;
while (i < len)
{
if (k == -1 || mstring[i - 1] == mstring[k])
{
next[i] = k + 1;
k++;
i++;
}
else
{
k = next[k];
}
}
}
int KMP(char* sum, char* mstring, int pos)
{
assert(sum);
assert(mstring);
int slen = strlen(sum);
int mlen = strlen(mstring);
int* next = (int*)malloc(sizeof(int) * mlen);
assert(next);
Getnext(next, mstring, mlen);//获取next数组
int i = pos;
int j = 0;
while (i < slen && j < mlen)
{
if (j == -1 || sum[i] == mstring[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
free(next);
if (j >= mlen)//这种情况说明匹配上了,返回对应的下标位置
{
return i - j;
}
return -1;//匹配不上返回-1
}
---内存相关函数---
函数介绍
void * memcpy ( void * destination, const void * source, size_t num );//num表示要拷贝多少个字节
这个函数的功能是拷贝数据,它跟strcpy的区别在于strcpy是仅仅对于字符串而言,而memcpy是对于全体类型可以使用
比如;拷贝int类型的数据
==易错点==:
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。(也就是说如果memcpy(a,a,sizeof(a))的结果是不确定的,取决于编译器)
模拟实现
这个函数的实现需要一定的指针基础,首先必须知道为什么要用void*的指针类型
void* 它就是一个泛型指针,什么类型的指针它都可以接收,像一个垃圾桶,只要是指针,你都可以往里扔,注意的是它是无法解引用操作的(*) |
==void* 可以接收任意指针==,所以对于这个函数它本身的要求就是要实现不管是什么类型的数据都能拷贝而言,它的选择是无可厚非的!
虽然void 的指针不能解引用操作,但是我们可以将它强转成char类型,然后一个字节一个字节的进行修改,这也就是它这个函数为什么要多一个参数num的原因,就是用来确定拷贝的字节次数。
(如果你理解过我之前发的模拟实现qsort这个函数,那这个函数对于你来说就是小菜一碟)
#include<assert.h>
void* my_memcpy(void* des, void* src, int num)
{
assert(des && src);
void* ret = des;//记录好目标空间的起始地址
while (num--)//拷贝
{
*((char*)des) = *((char*)src);
des = ((char*)des) + 1;
src = ((char*)src) + 1;
}
return ret;
}
函数介绍
void * memmove ( void * destination, const void * source, size_t num );
这个函数的功能跟上面那个memcpy是类似的,都是拷贝任意类型的数据
但是它区别与memcpy来说,它是可以实现内存块重叠拷贝的
比如:在very后面 拷贝 very useful(内存块重叠的)
==易错点==:
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理
模拟实现
我们说memmove跟memcpy其实最大的区别在于memmove可以拷贝内存重叠的,而memcpy却不能,所以在模拟实现的时候,其实大部分到一样,只是要在memcpy上稍作处理即可!
在实现memcpy的时候都是从前往后拷贝的,这个时候在处理相同空间的时候,可以会出现覆盖拷贝
所以我们可以分两大类情况
相等的情况下,两种方式都可以!!!
void* my_memmove(void* des, const void* src, int num)
{
assert(des && src);
void* ret = des;//记录好目标空间的起始地址
if (des < src)//如果是同一个空间的,当src的地址比des大,则从前往后拷贝
{
while (num--)
{
*((char*)des) = *((char*)src);
des = ((char*)des) + 1;
src = ((char*)src) + 1;
}
}
else//如果是同一个空间的,当src的地址比des小,则从后往前拷贝
{
while (num--)
{
*((char*)des + num) = *((char*)src + num);
}
}
return ret;
}
本章完!!!