探索字符与字符串:基本库函数的使用(二)

目录

文章目录

前言

函数模拟实现

strlen

strcpy

strcat

 strstr

strcmp

 memcpy

memmove

总结


前言

继接上文,本片文章我将带领大家去模拟实现一些基本的库函数。


函数模拟实现

strlen

前文我们已将基本了解了strlen函数是用于计算字符串长度的,那么接下来我们来模拟实现一下该函数

strlen函数统计的是\0之前的字符个数,函数返回类型是int,这里我们有三种实现方法:

方法一:计数器

int my_strlen(const char *ch)
{int i = 0;while(*ch != '\0') {ch++;i++;}return i;
}

 方法二:递归

int my_strlen(const char * str)
{
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}

 方法三:指针运算

int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}

 这些方法的代码实现都较为简单,就不再细介绍。

strcpy

在模拟实现strcpy函数时需要注意以下几点:

  • 函数参数顺序
  • 函数的功能,停止条件
  • assert
  • const修饰指针
  • 函数返回值

 我们再来看一下函数的原型:

char* strcpy(char * destination, const char * source );

 注意const修饰的参数模拟实现代码:

char *my_strcpy(char* dest, const char* src)
{assert(src&&dest);char* ret = dest;while (*dest++ = *src++){;}return ret;
}

 assert断言,为了防止传进来为NULL,后置++的优先级高于*,所以在*dest++中先后置++,再解引用。当赋值到\0时循环就会结束,返回目标字符串的起始位置。

strcat

函数的原型与strcpy函数原型类似。

strcat函数的功能是连接两个字符串,我们可以依据strcpy的思路将源字符串的内容copy到目标字符串的末尾,我们只需找到目标字符串中\0的位置即可

代码实现:

char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}

 那为什么strcat最好不要自己给自己追加。

看到模拟实现的strcat我们或许就会发现问题:

如果自己给自己追加,初始时dest和src就指向同一个字符串,当dest 向后移动找到\0以后就会停止(需要追加的位置),然后就会开始追加,将src指向的字符赋给dest指向的位置,这就会把\0给替换掉:

 但是\0对于src来说又很重要,这是循环结束的标志,而此时\0被替换,循环就无法停止,便会一直向后赋值,知道越界访问程序崩溃停止。

 strstr

strstr函数的功能是在一个字符串中查找指定的子串,并返回该子串在原字符串中的起始位置。

函数原型:

char* strstr(const char* str1, const char* str2);

 strstr在不依靠库函数的情况下,对其模拟实现可能有点复杂。

具体代码:

char* my_strstr(char* str1, char* str2)
{char* s1 = str1;char* s2 = str2;char* cp = str1;while (*cp){s1 = cp;s2 = str2;while (*s1 && * s2 && *s1 == *s2){s1++;s2++;}if (*s2 == '\0')return cp;cp++;}return NULL;
}

以a b b b c d e f和b b c为例:

 初始时两指针指向两字符串的首元素地址,问题在于要确定开始匹配的位置,那么我们就需要一个指针来存放开始匹配的位置,我们设为cp,初始阶段开始匹配,cp指向的位置和str1相同。

cp不断的向后寻找开始匹配的位置,如果*cp=\0,就说明cp遍历了整个字符串都没有找到匹配点,说明str2不属于str1的子字符串,返回NULL。

以此为前提才能开始匹配。从第一个字符开始向后匹配,匹配失败后我们就需要移动cp向后移动尝试从下一个位置开始,所以进行cp++;

s1和s2是进行字符比对的指针,起初s1和s2都是从字符串的首元素开始:

 然后s1和s2同步进行移动,对字符进行一一比对,

while (*s1 == *s2){s1++;s2++;}

 如果匹配失败,cp就向后移动,s1从cp指向的位置开始继续比对(s1=cp),s2从str2指向的字符串的首元素开始(s2=str2),如果匹配成功,此时的s2指向的一定是字符串末尾的\0由此我们就可以以此为判定条件,判断是否匹配成功:

while (*s1 && * s2 && *s1 == *s2){s1++;s2++;}if (*s2 == '\0')return cp;

匹配成功就可以提前结束程序,返回首次匹配成功的初始位置(return cp)。遍历结束都没有找到则返回空指针。

strcmp

strcmp函数的功能是比较两字符串大小。

具体功能:

  • 比较两个字符串的大小。
  • 返回一个整数值,表示两个字符串的大小关系。
  • 如果字符串相等,返回值为0。
  • 如果字符串1大于字符串2,返回值大于0。
  • 如果字符串1小于字符串2,返回值小于0

 由此我们来模拟实现一下,首先我们依次比对两个字符串对应位置字符的大小。如果有一个遍历结束,就返回0。但切记while循环里不可以这样写*str1++ == *str2++。

如果str1和str2不相等退出循环后仍会向后走一步,这样就跳过了对应位置上第一个不相等的字符,就会导致在判断大小时出现错误。

所以我们选择在循环里进行++操作。

在两字符不相等的前提下,如果str1指向的字符大于str2指向的字符,就返回1,否则就返回-1

int  my_strcmp(const char* str1,const char* str2)
{assert(str1 && str2);while (*str1 == *str2){if (*str1 == '\0')return 0;str1++;str2++;}if (*str1 > *str2)return 1;elsereturn -1;

 memcpy

函数原型:

void * memcpy ( void * destination, const void * source, size_t num );

从source的位置开始向后复制num个字节的数据到destination的内存位置。

此外我们还要注意:

  • 这个函数在遇到 '\0' 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的。

 这里注意,memcpy的函数类型是void* 类型,这表明memcpy函数可以用于所以类型的数据。

那么在模拟实现时,我们就要设计一个可以适用于任何类型的函数。最终的返回结果是目标dest数据的起始地址。

于是我们就可以这样设计

模拟代码演示:

void* my_memcpy(void* dest, void* src, size_t num)
{void* ret = dest;assert(dest && src);while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}return ret;}

 num为需要copy的字节大小,可以作为循环的次数,不管传入任何类型的数据,我们都将其转换为char*类型,进行一个字节一个字节的复制。最终返回目标dest数据的起始地址。只需创建一个变量存储即可。

memmove

函数原型:

void* memmove(void* dest, const void* src, size_t n);

 dest是目标内存的起始地址,src是源内存的起始地址,n是要移动的字节数

注意:函数原型中的参数类型都是void*,表示传入的参数是任意类型的指针,可以用于处理不同类型的数据。返回值类型是void*,表示返回一个指向目标内存的指针。

那么我们要如何设计呢?

有人可能会想,这个简单,我们模仿上边的memcpy函数进行复制就好了,但这样真的可以吗?

在模拟的memcpy中确实可以实现部分的数据移动,但它并不完善。这里我们需要考虑到数据覆盖问题。

如果是将数据从高地址移动到同一数组的低地址处,这样可以实现如:

memcpy(arr, arr + 2, 20);

但如果是将前边的数据向后移动呢?

memcpy(arr+2, arr , 20);

当我们想把arr处20个字节的数据向后移动2个单位时,1赋值给了3,2赋值给了4,那到3赋值时3的值就已经被覆盖,这就会导致最终结果错误。

那我们应该怎么解决呢?

这时我们就可以考虑从源数据段的最后边开始赋值,20个字节,5个元素,先将5赋给7,4赋给6,3赋给5……

 

这样就可以实现向后移动。

回到memmove函数,它是一个可以接收任何类型数据的函数,那么次时我们就需要将数据类型转换为char*类型,进行一个字节一个字节的赋值。

代码实现:

my_memmove(void* dest, const void* src, size_t n)
{if (dest < src){while (n--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{while (n--){*((char*)dest+n) = *((char*)src+n);}}}

 根据src和dest的大小进行判断,选择合适的方法。

(char*)dest+n和(char*)src+n将两个指针移动到需要后移的数据最后,从后向前依次赋值。每移动一个字节,n--,dest和src从最后向前一个字节,继续赋值……,直到需要移动的字节移动完毕停止。


总结

好的本期内容到此结束,模拟实现这些内容虽然很不常用,但是却可以帮助我们更好的理解和正确的使用这些库函数,同样也可以帮助我们提升一定的算法能力,对于处理一些字符操作题目很有帮助。最后,感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/23124.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 docker-compose 部署 Jenkins

注&#xff1a;我是在虚拟机&#xff08;Ubuntu&#xff09;上部署了 docker-compose&#xff0c;然后才使用 docker-compose 部署 Jenkins&#xff01; 关于如何在 Ubuntu 部署 docker-compose&#xff0c;可以看我其它的文章。 本文目录 1. 创建 docker_jenkins_compose 目录…

Modbus tcp转ETHERCAT网关modbus tcp功能码

远创智控YC-ECT-TCP网关能够连接到Modbus tcp总线和ETHERCAT总线中&#xff0c;实现两种不同协议设备之间的通讯。这个网关能够大大提高工业生产的效率和生产效益&#xff0c;让生产变得更加智能化。远创智控YC-ECT-TCP 是自主研发的一款 ETHERCAT 从站功能的通讯网关。该产品主…

小程序Url Link跳转怎么获取query参数?

onLoad(options){if (options) {let value1 decodeURIComponent(options.value1)let value2 decodeURIComponent(options.value2)...调用后台接口查询数据} } 我是通过这种方式接收参数的&#xff0c;如果想验证可以通过编译器模拟&#xff1a;

深度学习标量、向量、矩阵、张量之间的区别与联系

文章目录 前言1、张量**注意**&#xff1a; 2、**标量** (scalar)&#xff1a;0阶的张量&#xff0c;0个轴&#xff0c;一个单独的数(整数或实数)&#xff1b;3、**向量**(vector)&#xff1a;1阶的张量&#xff0c;也叫矢量&#xff0c;1个轴&#xff0c;一个数组&#xff1b;…

Openlayers实战:多地图底图切换

在实际的地图项目中,不管是我们看到的百度地图还是高德地图等,都会有地图切换这一项。 在Openlayers实战中,我们用三种地图做demo,分别是谷歌地图。Openstreetmap,stamen地图。 切换的主要原则是设置三个底图层,设定其显示状态,用到了visible这一个属性。 效果图 源代码…

【Visual Studio】在 Windows 上使用 Visual Studio 构建 VTK

知识不是单独的&#xff0c;一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏&#xff1a;Visual Studio。 编号内容1【Visual Studio】在 Windows 上使用 Visual Studio 构建 VTK2【Visual Studio】在 Windows 上使用 Visual Studio 配合 Qt 构建 VTK3【VTK】VTK 显…

vue3中通过vue-i18n实现国际化

效果图 前言 突然想在vue3项目中使用国际化功能&#xff0c;查阅相关资料后发现和vue2的用法有些出入&#xff0c;记录一下 使用 下载vue-i18n npm i vue-i18n2、准备语言文件 目前我的项目只支持中英文切换&#xff0c;故准备一份中文文件和一份对应的英译文件 创建langur…

Kubernetes 组件介绍

Kubernetes 组件 部署完 Kubernetes&#xff0c;便拥有了一个完整的集群 一组工作机器&#xff0c;称为节点&#xff0c; 会运行容器化应用程序。每个集群至少有一个工作节点 工作节点会托管 Pod &#xff0c;而 Pod 就是作为应用负载的组件。 控制平面管理集群中的工作节点…

[QT编程系列-13]:QT快速学习 - 1- 初识

目录 第1章 QT的介绍 1.1 QT VS MFC 1.2 QT历史 1.3 QT的应用 1.4 QT学习方法 1.5 QT对象树 1.6 2-8定律 1.7 QT优势&#xff1a; 1.8 QT支持的平台 第2章 QT UI是各种控件对象的堆积 第3章 QT UI是各种控件的堆积 第4章 控件窗口的控制 第1章 QT的介绍 1.1 QT V…

天翎MyApps低代码平台案例分享—阿米检测设备管理系统

项目背景&#xff1a;阿米检测技术有限公司&#xff08;以下简称为“阿米检测”&#xff09;隶属于中国航天科技集团&#xff0c;是北京航天计量测试技术研究所下属全资公司&#xff0c;2018年由国家财政部正式发文批准成立。司转化航天高端技术&#xff0c;开展测量方法应用、…

PyTorch翻译官网教程6-AUTOMATIC DIFFERENTIATION WITH TORCH.AUTOGRAD

官网链接 Automatic Differentiation with torch.autograd — PyTorch Tutorials 2.0.1cu117 documentation 使用TORCH.AUTOGRAD 自动微分 当训练神经网络时&#xff0c;最常用的算法是方向传播算法。在该算法中&#xff0c;根据损失函数与给定参数的梯度来调整模型参数&…

979. 在二叉树中分配硬币(力扣)

在二叉树中分配硬币 题目一题一解&#xff1a;DFS(java)思路步骤解析测试代码复杂度分析运行结果 优化代码思路测试代码运行结果复杂度分析 题目 给你一个有 n 个结点的二叉树的根结点 root &#xff0c;其中树中每个结点 node 都对应有 node.val 枚硬币。整棵树上一共有 n 枚…