当前位置: 首页 > news >正文

字符串函数(2)

目录

  • 前言
  • 1. strlen
    • 1.1 strlen函数的理解和使用
    • 1.2 strlen函数的模拟实现
  • 2. strcpy
    • 2.1 strcpy函数的理解和使用
    • 2.2 strcpy函数的模拟实现
  • 3.strcat
    • 3.1 strcat函数的理解和使用
    • 3.2 strcat 函数的模拟实现

前言

在上一篇文章中,我们对字符分类函数和字符转换函数进行了学习,部分小伙伴们可能会想字符串函数在哪里呢?其实是我没有写完啦!在本篇文章中,我会对该内容进行详细讲解。
在这里插入图片描述

1. strlen

1.1 strlen函数的理解和使用

字符串函数的使用需要包含头文件string.h。strlen的返回值的类型是size_t(无符号整型)。strlen函数是求字符串的长度的。
例如:
在这里插入图片描述

工作原理是:
当我们有一个字符串时,里面除了我们没看到的以外,还存放着\0。我们给strlen函数传了数组名arr。数组名是数组首元素的地址,strlen从给定的起始位置开始,向后统计\0之前字符的个数。
注意:
一定是从起始位置开始,我们现在可以测试一下别的写法。
在这里插入图片描述
arr是数组名,代表首元素的地址,如果传arr,则从a开始数。如果传arr+1,则跳过一个元素,从b开始数。
前面我们说过strlen的返回值的类型是size_t类型的,这一部分我们需要加深理解,现在我们举一个例子进行理解。
在这里插入图片描述
按照我们的想法来看,strlen(arr2)和strlen(arr1)的输出结果分别为3和6,3减6的值等于-3,应该打印<=。但是运行的结果并不是我们所想,因为strlen的返回值的类型是size_t , size_t类型的值相减也是size_t,此时计算出的-3会被作为一个无符号的整型来处理,此时的-3就没有了符号位。
在这里插入图片描述
在内存中存放着-3的补码,当-3为无符号整型时,最前面的1就不是符号位了,此时就不存在原反补了。计算的结果将是一个非常大的正数,所以输出的结果是>。我们现在对代码进行修改。

#include<stdio.h>
#include<string.h>
int main()
{char arr1[] = "abcdef";char arr2[] = "abc";if (strlen(arr2) > strlen(arr1) )printf(">\n");elseprintf("<=\n");return 0;
}

或者:

#include<stdio.h>
#include<string.h>
int main()
{char arr1[] = "abcdef";char arr2[] = "abc";if ((int)strlen(arr2) - (int)strlen(arr1) > 0)printf(">\n");elseprintf("<=\n");return 0;
}

这两个代码都能实现我们原来的目的。

1.2 strlen函数的模拟实现

我们采用递归的方式进行实现。
现在我们有一个字符数组里面存放着abcdef。现在我们需要写一个自己的函数来计算字符串的长度。仿照着strlen把arr传过去,并让其返回一个size_t的值,当然int也行,只是字符串的长度不会为负值,选择size_t比较合适。
函数的参数部分需要接受arr(即首元素的地址),我们采用char*类型的str进行接收,同时我们不希望str去改变字符串,使用可以使用const来修饰。
递归的思想是大化小,我们把字符串中的第一个字符拿出来,不是\0说明长度至少是1,1加上后面的长度就是总长度,按照这个原理我们不断的向后拿取,如图
在这里插入图片描述
代码:

#include<stdio.h>
size_t my_strlen(const char* str)
{if (*str == '\0')return 0;elsereturn 1 + my_strlen(str + 1);
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd\n", len);return 0;
}

运行结果:
在这里插入图片描述

现在可能还有部分小伙伴没有理解,我们可以缩短字符串的长度(改为abc)来理解:
在这里插入图片描述

我们要计算abc的长度,此处我们调用了自己写的函数来完成。我们将a的地址传了过去,给了str,此时str向后就可以看到a b c \0。
刚开始时,* sr 为a,a并不等于\0,我们就走else这条路,然后再对my_strlen进行调用。第二次调用时,这里的str指向b,b不等于\0,走else的路线,这里我们还需要对my_strlen进行调用,第三次调用时,这里的str指向c,c不等于\0,走else的路线,最后一次调用my_strlen时,str指向了\0。此时我们的递推就结束了,将开始回归。1就会不断的相加到3,最后返回去,此时len就为3.
注意:
在这里的 1 + my_strlen(str + 1)没有返回时,是不会计算的。
代码:

#include<stdio.h>
size_t my_strlen(const char* str)
{if (*str == '\0')return 0;elsereturn 1 + my_strlen(str + 1);
}int main()
{char arr[] = "abc";size_t len = my_strlen(arr);printf("%zd\n", len);return 0;
}

2. strcpy

2.1 strcpy函数的理解和使用

该函数是用来进行字符串拷贝的。我们现在来看函数的基本情况。
在这里插入图片描述
第一个参数的名字是destination(目的地)。第二个参数是source(源头),即将源头的数据拷贝到destination里面去。例如:
在这里插入图片描述
理解:
arr1数组里面放了hello world\0 .我们把arr1和arr2分别传给了我们刚才说的source和destination,我们认为它是一个个字符进行拷贝的,把\0拷贝完成就停止。
这里我们可以看看到底有没有将\0拷贝过去,如下:
在这里插入图片描述
此时我们可以看到\0也被拷贝过去了。既然我们需要将\0拷贝过去,那么我们源头中的字符串也需要有\0。如果我们去掉\0会发生什么呢?如下:

在这里插入图片描述
拷贝将不会有停下的动作,一直进行拷贝,甚至会越界往后进行修改。
注意:

  1. 在拷贝时,我们的目标空间必须足够大,只有足够大,才能放下从源头拷贝过来的数据
  2. 目标空间必须可修改,如图:
    在这里插入图片描述
    这里的p是常量字符串,不能进行修改。

2.2 strcpy函数的模拟实现

我们首先创建arr1,里面放上abcdef,再创建一个arr2,里面可以放上20个元素。根据刚才学到的strcpy的参数部分,我们也可以将my_strlen中的参数设置为char * dest 和char * src。暂时我们把返回这里写为void。
我们将arr2和arr1传过去之后,dest和src分别指向如下位置:
在这里插入图片描述
这里我们需要将字符一个个拷贝过去,即我们需要进行多次拷贝,我们可以写一个while循环,在里面使dest和src++来不停的往后。当\0也拷贝下来时循环结束,所以我们把条件写为 * src! = ’\0’,此时\0并没有拷贝下来,我们添加 *dest = * src即可(停下来时 * src为\0,我们此时相当于把‘\0’拷贝过去)。此时代码就完成了。

代码:

#include<stdio.h>
void my_strcpy(char* dest, char* src)
{while (*src != '\0'){*dest = *src;src++;dest++;}*dest = *src;
}int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };my_strcpy(arr2, arr1);printf("%s\n",arr2);return 0;
}

运行结果:
在这里插入图片描述
现在我们进行优化,把代码的改为后置++,用完后再++,然后我们的 * dest++ = * src++和 * dest = * src是有一点重复的,我们需要改为如下写法:

#include<stdio.h>
void my_strcpy(char* dest, char* src)
{while (*dest++ = *src++){;}
}int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };my_strcpy(arr2, arr1);printf("%s\n",arr2);return 0;
}

理解:
第一次 * src是a,我们将a赋值过去,此时整个表达式的值就为a,a的ASCII码值不为0,为真,进入循环,因为赋值完成以后进行++,则下一次 * src拿到的值就是b,然后再进行该过程,直到 * src指向\0,将\0赋值过去,此时表达式的结果为\0,\0的ASCII码为0,0为假,跳出循环。我们会发现这里我们既可以赋值,赋值产生的值有可以进行判断。
我们还可以对代码进行调整,我们的目的是将src指向的内容拷贝到dest指向的空间里面去,我们不希望src指向的空间被修改。这里可以加const进行修饰,但是解引用之前我们害怕为空指针,我们可以利用assert断言进行处理。

代码:

#include<stdio.h>
#include<assert.h>
void my_strcpy(char* dest, const char* src)
{assert(dest && src);while (*dest++ = *src++){;}
}int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };my_strcpy(arr2, arr1);printf("%s\n",arr2);return 0;
}

但是strcpy的返回值的类型是char*,与我们自己写的函数是不同的,那使用char * 有什么好处吗?
函数是将原字符串拷贝放到目标空间里面去,我们是期望目标空间发生变化来进行观察。所以函数最终返回目标空间的起始位置。但是我们这里不能直接return dest。因为这里dest不断地++以后,dest指向的不再是起始地址,解决方式是我们创建一个 char * 的ret把未进行改动的dest存一份,最后return ret。

代码:

#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{char* ret = dest;assert(dest && src);while (*dest++ = *src++){;}return ret;
}int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };char* ret = my_strcpy(arr2, arr1);printf("%s\n",ret);return 0;
}

运行结果:
在这里插入图片描述
此时我们的函数更加灵活,my_strcpy的返回值也可以作为其他函数的参数,实现链式访问。

代码:

#include<stdio.h>
#include<assert.h>
#include<string.h>
char* my_strcpy(char* dest, const char* src)
{char* ret = dest;assert(dest && src);while (*dest++ = *src++){;}return ret;
}int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };/*char* ret = my_strcpy(arr2, arr1);*/size_t len = strlen(my_strcpy(arr2, arr1));printf("%zd\n",len);return 0;
}

运行结果:
在这里插入图片描述

3.strcat

3.1 strcat函数的理解和使用

该函数的作用是字符串的追加,我们直接上实例进行理解:
在这里插入图片描述

我们现在有一个数组arr1,里面存放着hello。我们想在后面追加world。可能有同学会想用strcpy,但是它会将hello给覆盖。所以以我们会使用strcat这个函数。

现在我们研究一下这个函数:
在这里插入图片描述
我们不难发现这与我们刚才看到的strcpy的参数是一样的,第一个参数是char * destination,是我们需要追加的对象,第二个参数是const char * source,是需要追加的内容。这个函数的返回值的路线是char *,返回的是目标空间的起始地址(与我们之前讲的strcpy一样)。
我们现在看看是如何追加的:
在这里插入图片描述
这里是在第一个\0的位置开始追加world,我们不免会想这里我们是将world的\0追加过来了,还是原来在arr1中的?我们可以测试看看,我们可以在原arr1中添加一个\0并观察试试。
在这里插入图片描述
在这里插入图片描述
两张图对比我们可以发现,world从hello后面的\0开始追加,world还把它原来的\0也带了过来。

strcat的原理:

  1. 找到目标空间中的第一个\0
  2. 然后从这个\0的位置开始追加源头字符串
  3. 源头字符串的内容,包括\0都会追加到目标空间

注意:
在追加时目标空间的大小要足够长,能够放下我们要追加的数据。其次目标空间要可修改,以便追加数据。因为会将源头字符串字符串的\0追加过去,所以我们的源头字符串要有\0。目标字符串也要有\0,不然不知道从哪里开始追加。

3.2 strcat 函数的模拟实现

我们之前观察过它的参数和返回类型,这些部分是和strcpy一样的。我们就可以仿照来写代码。
我们首先需要在目标空间中找到\0再进行追加,这里我们可以利用while循环往后找,如果 * dest不等于\0,我们就让dest++,直到dest指向\0跳出循环。
之后我们需要将world这个数据拷贝过去 ,这个拷贝数据的代码我们观察已经学习过了,我们可以照搬过来。刚才dest已经指向了\0,现在我们进行拷贝就会将\0覆盖。
接下来我们要返回char * 的数据,即返回目标空间的起始地址,但是我们的dest通过++已经走很远了,我们可以仿照刚才的ret来存放目标空间的起始地址,最后返回ret。当然如果担心我们的指针有效性也可以采用assert断言一下。

代码:

#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{char* ret = dest;assert(dest && src);while (*dest != '\0')dest++;while (*dest++ = *src++){;}return ret;
}int main()
{char arr1[20] = "hello ";my_strcat(arr1, "world");printf("%s\n", arr1);return 0;
}

运行结果:
在这里插入图片描述
思考:
我们能不能自己给自己追加?
进行传参以后我们的dest和src指向的位置为:
在这里插入图片描述

程序开始运行以后我们的dest会一直往后找,直到找到\0,之后开始拷贝,此时h就会将原来的\0覆盖,没有\0以后拷贝的这个循环是停不下来的,就会造成死循环的问题。
在这里插入图片描述
所以我们通常是不会用这个函数进行对自己追加的操作的。strcat在进行自己给自己追加时会有一定的概率会成功,我们如果想要自己给自己追加可以使用strncat。
好了今天知识就学习到这里,我们下期blog再见!如果文章内容有误,请大佬在评论区斧正!谢谢大家!
在这里插入图片描述


http://www.mrgr.cn/news/33123.html

相关文章:

  • conda创建 、查看、 激活、删除 python 虚拟环境
  • 将大型语言模型(如GPT-4)微调用于文本续写任务
  • 动手学深度学习10.1. 注意力提示-笔记练习(PyTorch)
  • wordpress站外调用指定ID分类下的推荐内容
  • Ruby编程语言全景解析:从基础到进阶
  • springboot自动装配
  • 【yolo破损纸板-包装盒-快递袋缺陷检测】
  • 《机器人SLAM导航核心技术与实战》第1季:第9章_视觉SLAM系统
  • 学习IEC 62055付费系统标准
  • 新版ssh客户端无法连接旧版服务器sshd的方法
  • 【学习笔记】手写Tomcat 四
  • 《AI时代程序员核心竞争力提升指南》
  • C++ 构造函数最佳实践
  • AUTOSAR汽车电子嵌入式编程精讲300篇-基于CAN总线的气动控制
  • 【linux-Day4】linux的基本指令<下>
  • 网络丢包定位记录(三)
  • XXL-JOB环境搭建
  • github加速下载@powershell命令行内加速下载github资源@获取镜像加速后的链接
  • 【成品论文】2024年华为杯研赛E题25页高质量成品论文(后续会更新
  • 代码编辑器 —— Notepad++ 实用技巧
  • 51单片机——独立按键
  • 一个安卓鸿蒙化工具
  • 银河麒麟桌面操作系统V10(SP1)离线升级SSH(OpenSSH)服务
  • CompletableFuture的allOf一定不要乱用!血泪史复盘
  • 01-ZYNQ linux开发环境安装,基于Petalinux2023.2和Vitis2023.2
  • go 安装依赖超时