C生万物 | 模拟实现库函数strcpy之梅开n度

news2024/9/25 3:23:54

在这里插入图片描述

文章目录

  • 【梅开一度】:观察库函数strcpy()的实现
  • 【梅开二度】:模仿实现strcpy()
  • 【梅开三度】:优化简练代码
  • 【梅开四度】:assert()断言拦截
  • 【梅开五度】:const修饰常量指针
  • 【梅开六度】:还可以有返回值哦🚀
  • 👴梅开n度后的忠告👴

【梅开一度】:观察库函数strcpy()的实现

首先我们先来观察一下库函数strcpy去实现字符串拷贝的功能

  • 首先来看看文档中对这个函数是如何描述的
    在这里插入图片描述
  • 清楚了这个库函数的功能之后,我们到VS中来看看使用代码如何实现
  • 可以看到,首先去定义出两个字符串:第一个str1为目标字符串,初始化均为x是为了在调试的时候方便查看是否拷贝成功;第二个str2为源头字符串
int main(void)
{
	char str1[10] = "xxxxxxxxx";
	char str2[] = "hello";

	strcpy(str1, str2);

	printf("%s\n", str1);
	return 0;
}
  • 进入调试进行观察可以发现,两个字符串已经初始化完成,准备进行拷贝

在这里插入图片描述

  • 然后看到如下就完成了字符串的一个拷贝工作,会连带\0一起拷贝过去。所以对于目标字符串我没有初始化为0就是为了看出拷贝完成的工作

在这里插入图片描述


【梅开二度】:模仿实现strcpy()

好,看完了库函数的实现之后,我们考虑自己去进行一个实现

  • 通过定义出一个my_strcpy()的函数,设置形参为两个字符指针,用于接收主函数传入进来的两个字符串的起始地址
void my_strcpy(char* dst, char* src)
  • 对于数组的函数名来说就是首元素地址,所以直接传入数组名即可
my_strcpy(str1, str2);
  • 写代码前我们来看一下字符串拷贝的原理,也就是获取到srcdst两个指针所指向的字符,然后进行一一拷贝,直到*src == '\0’ 为止

在这里插入图片描述

  • 所以对于一个字符的拷贝就可以这样去写
*dst = *src;
  • 但是拷贝完一个字符之后还要拷贝后面的字符,这就是通过字符串指针去进行一个后移的操作,便可以进行继续拷贝

在这里插入图片描述

  • 最后当这个*src == '\0'的时候,便结束拷贝,跳出循环。此时我们还有最后一个'\0'还没有拷贝过去,继续执行一次*dst = *src即可

代码展示

void my_strcpy(char* dst, char* src)
{
	while (*src != '\0')
	{
		*dst = *src;
		src++;
		dst++;
	}
	*dst = *src;
}

运行结果展示

在这里插入图片描述


【梅开三度】:优化简练代码

看完了上面这段代码,你认为就结束了吗?其实对于这种代码来说是不够简练的,我们来继续进行一个优化

  • 对于while循环内部的判断,我们知道是一个逻辑表达式,而对于'\0'来说就相当于与【假】,所以当*src != '\0'的时候就会一直循环,就为【真】。所以我们可以直接改成*src,当其碰到'\0'的时候就会跳出循环停止拷贝
while (*src)
  • 第二处可以优化的就是循环内部的一个拷贝的过程,因为在每一次拷贝完成之后两个字符指针就会进行一个后移,此时我们可以对它们进行一些合并。
  • 因为对于后置++来说是先执行++之前的,所以赋值完成之后再++就刚好可以达到一个后移的效果
*dst++ = *src++;

来看一下代码的优化后的逻辑,其实它还可以再进行一个优化🐉

while (*src)
{
	*dst++ = *src++;
}
*dst = *src;
  • 通过仔细观察库函数strcpy()的描述后就可以发现,其实它在拷贝结束之后也是存在返回值的,返回的就是拷贝完成之后的目标字符串

在这里插入图片描述

  • 因此我们可以将拷贝的逻辑也放到循环的条件判断中去,不需要在最后继续拷贝'\0',因为在循环中拷贝完之后while()循环中就是那个'\0',会自动跳出循环,此时【src】和【dst】也已经遍历结束
  • 所以代码就被简化成了下面这样👇
while (*dst++ = *src++)
{
	;
}

运行结果展示

在这里插入图片描述


【梅开四度】:assert()断言拦截

经过上面的众多优化,你一定觉得可以了,确实已经是够简洁了,但是呢却缺乏安全性🛡

  • 我们是模拟实现字符串的拷贝,将str2中的字符串拷贝到str1中,那就是要源头字符串中有内容才可以拷贝,但若是我将这个str置为NULL然后传进去呢,会发生什么?
char* str2 = NULL;

在这里插入图片描述

  • 通过运行可以看到,运行的时候报出了[空指针异常],因为在函数内部现在要执行*src,也就是解引用的操作,我们知道对于空指针来说是不能解引用的,因此这里就出现问题了,表示我们的程序考虑地不够严谨
  • 此时就可以使用到一样东西叫做【断言】,可以去看看官方文档 👉assert

在这里插入图片描述

assert(src != NULL);
  • 若是加上了这句assert断言,那么编译器在运行的时候就会报出对应的错误信息,括号里面要写上的就是出错的对立面,若是当src != NULL时,便不会执行这个断言,只有当src传入进来是NULL的时候才会触发这个断言
  • 当然为了方便也可以写成这样👉assert(src);只有里面的表达式expression为真的时候才会执行,为假的时候便不会执行
  • 也可以给dst加上断言,防止它传入进来也为NULL,👉assert(dst);

那么这两个断言的逻辑就可以转换为只有当srcdst均为非空的时候程序才正常执行,只要有一方为空便报出错误,那便将它们做一个合并,就可以想到使用我们在操作符章节讲到过的【逻辑与】

assert(dst && src);

在这里插入图片描述


【梅开五度】:const修饰常量指针

看完了上面的这些,那你一定会觉得这个这个代码非常严谨了吧,但是不要高兴得太早,还有问题😮

假设一个公司的程序员,它现在就在模拟实现一个字符串strcpy(),也想到了断言这一步,然后吃饭去了。和朋友一起到楼下酒吧喝了两杯,然后呢回到公司之后继续写业务,要知道此时他喝醉了🍺

while (*src++ = *dst++)
{
	;
}
  • 于是呢他就将代码写成了上面这样,将目标字符串dst中的内容拷贝到了原字符串src中,此时虽然在拷贝的过程中不会出现什么问题,可是呢在运行的时候就会出现【变量str周围的堆栈已损坏】,也就是【str1】中的这些“xxxxxxxxx”若是拷贝到str2中是存不下的,这就出现问题了

在这里插入图片描述

  • 那么上述的这个程序员的操作其实是在修改源头字符串src那我们要将原字符串拷贝到目标字符串中,原字符串肯定不能修改,所以这个时候就要使用到【const常】了。此时我们可以在char* src的前面加上一个const作为修饰,此时若是这个喝醉酒的程序员把拷贝的字符串反了,编译时期就会直接报出错误

在这里插入图片描述

  • 此时对于src来说就叫做【常量指针】,它所指向的内容是不可以修改的,但是它的指向是可以修改的,若是不太清楚可以看看这篇文章👉常量指针与指针常量

可能有同学说,就这么一个小小的const也这么讲究,那我要和你说:我们写业务逻辑就是要严谨,你永远不可能知道用户下一秒会做什么。加上了const之后使得我们的代码更具有健壮性💪防止源头被修改,也就可以扼杀一个运行时错误❌


【梅开六度】:还可以有返回值哦🚀

最后的话再进行一个完善也就是我们前面说到过的有关这个strcpy()函数还具有一个返回值,也就是char*,返回的是【dst】拷贝后的内容

在这里插入图片描述

  • 因为我们是进行一个模拟,所以为了尽量和原本的内容保持一致,我们也要将这个返回值加上,这个很简单,只需要在一开始的时候保存一下src原字符串即可
char* ret = src;
  • 最后将其返回即可
return ret;
  • 以下便是整体代码展示
char* my_strcpy(char* dst, const char* src)
{
	assert(dst && src);

	char* ret = src;
	while (*dst++ = *src++)
	{
		;
	}
	return ret;
}
int main(void)
{
	char str1[10] = "xxxxxxxxx";
	char str2[] = "hello";

	printf("str1 = %s\n", my_strcpy(str1, str2));
	return 0;
}

到这里,我么的模拟实现就算是真正完成了,相信在跟着我一步步地这么思考下来,一点点地做修改,完成代码。回顾整个流程。相信你的逻辑思维一定得到了提升,更加严密💪

👴梅开n度后的忠告👴

为何以梅开n度作为标题,一方面除了【吸睛】之外,其实也在反映我们的程序人生🚶

  • 其实做我们程序员这一行,20%在写业务逻辑,但是80%在调BUG,修BUG,但其实这都是你的代码问题导致的,若是我们在第一次写代码的时候就将问题考虑得很仔细、很周全,其实是可以减轻很多负担的
  • 当别人五点已经下班的时候,你还在吭哧吭哧修BUG,也就造成了【996】的现象,若是不想变成这样,那就提高你的代码质量吧!

2023年2月18日记,有感而发,还望采纳❤

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用linux服务器多核跑程序和unhashable type: ‘list‘报错的解决方案

问题描述 在使用服务器多核跑程序的时候,需要把核心的程序抽取出来,然后提供迭代参数。然后就可以使用多核去跑程序了。但是在执行的过程中报错如下: Exception has occurred: TypeError unhashable type: list File "/home/LIST_208…

【机器学习 深度学习】通俗讲解集成学习算法

目录:集成学习一、机器学习中的集成学习1.1 定义1.2 分类器(Classifier)1.2.1 决策树分类器1.2.2 朴素贝叶斯分类器1.2.3 AdaBoost算法1.2.4 支持向量机1.2.5 K近邻算法1.3 集成学习方法1.3.1 自助聚合(Bagging)1.3.2 提升法(Boosting)1.3.2.1 自适应adaboost1.3.3 …

【C语言编译器】02 Windows下 7 种C语言IDE的使用(万字长文警告,含Visual Studio多个版本)

目录一、Visual Studio1.1 VS 20101.2 VS 20151.21 简介1.22 使用1.3 VS 20171.31 简介1.32 使用1.4 VS 20191.41 简介1.42 使用1.5 VS 20221.6 VS 安全函数问题1.7 VS “无法查找或打开PDB文件” 问题二、CLion2.1 CLion简介及安装2.2 使用CLion编写C程序三、Dev C3.1 Dev C简…

【ubuntu 22.04不识别ch340串口】

这个真是挺无语的,发现国内厂商普遍对开源环境不感兴趣,ch340官方linux驱动好像被厂家忘了,现在放出来的驱动还是上古内核版本: 于是,驱动居然要用户自己编译安装。。还好网上有不少大神:链接,…

一起学 pixijs(3):Sprite

大家好,我是前端西瓜哥。今天来学习 pixijs 的 Sprite。 Sprite pixijs 的 Sprite 类用于将一些纹理(Texture)渲染到屏幕上。 Sprite 直译为 “精灵”,是游戏开发中常见的术语,就是将一个角色的多个动作放到一个图片…

零基础小白如何学会Java?

Java作为目前使用最广泛的编程语言,自身在常见的企业级业务应用程序以及Android应用程序等方面都有突出的表现。作为跨平台语言,具有安全性、易用性、通用性等特点,被特意设计用于互联网的分布式环境。 对于很多喜欢代码的小伙伴来说Java都是…

音乐播放器-- 以及数据库数据存储

运行环境 : java1.8 数据库以及代码编写工具 : sqlserver -- mysql 也可以 工具 eclipse 编码gbk窗体 : Swing使用了jaudiotagger 进行了音乐处理 图片展示 ----- 空闲时间 做出来玩的项目 部分功能还没有完善 完善了的功能 音乐 /// 主页 &a…

SheetJS的部分操作

成文时间:2023年2月18日 使用版本:"xlsx": "^0.18.5" 碎碎念: 有错请指正。 这个库自说自话升级到0.19。旧版的文档我记得当时是直接写在github的README上。 我不太会使用github,现在我不知道去哪里可以找到…

SpringMvc介绍。

目录 1、SpringMvc概述 1、基本介绍 2、工作流程 3、bean加载控制 二、请求 1、请求映射路径 2、请求方式 3、请求参数 4、请求参数(传递json数据) 5、日期类型参数传递 三、响应 四、REST风格 1、REST简介 2、RESTful入门案例 3、RESTfu…

信号完整性设计规则之串扰最小化

本文内容从《信号完整性与电源完整性分析》整理而来,加入了自己的理解,如有错误,欢迎批评指正。 1. 对于微带线和带状线,保持相邻信号路径的间距至少为线宽的2倍。 减小串扰的一种方式就是增大线间距,使线间距等于线…

GeniE 实用教程(三)属性

目 录一、前言二、材料属性三、截面属性3.1 梁横截面3.2 板壳厚度3.3 截面赋予四、截面偏置4.1 梁偏置4.2 板壳偏置五、局部轴方向5.1 梁的局部轴5.2 板壳的法向六、水力属性6.1 湿表面属性6.2 水动力参数七、参考文献一、前言 SESAM (Super Element Structure Anal…

23 pandas Excel文件的拆分与合并

文章目录一个文件夹下多个工作簿的合并【单独Sheet】同一工作簿中多个Sheet合并ExcelWriter针对不同工作表的操作将一个工作表拆分成多个工作表将一个工作表拆分成多个工作簿一个文件夹下多个工作簿的合并【单独Sheet】 1把文件夹下所有的文件都遍历出来2循环读取文件放入一个…

【C++】再谈vscode界面调试C++程序(linux) - 知识点目录

再谈vscode界面调试C程序(linux) 配套文档:vscode界面调试C程序(linux) 命令解释 g -g ../main.cpp 编译main.cpp文件; -g:生成调试信息。编译器会在可执行文件中嵌入符号表和源代码文件名&…

程序员必备的技能-深入理解 Linux 内核拆解

841 页的《深入理解 Linux内核》堪称经典,时隔多年打开,泛黄的纸张上面仍然跳跃出一个个让人心潮澎湃的知识点,突然让我想起一位微信朋友的昵称:知识的舔狗!拆,开始~前言第一章 绪论Linux与其他类Unix内核…

springmvc汽车企业公司网站的系统设计 java ssm

红旗汽车走进社区,走进生活,成为当今生活中不可缺少的一部分。随着汽车行业的发展,加强管理和规范管理司促进红旗汽车网站健康发展的重要推动力。在我国迎来良好的发展机遇,但同时也确实有许多问题的需要研究和探讨。系统主要完成…

pvs中pv显示[unknown]解决方法、正确剔除一个vg流程方法【不影响vg已有的lv数据】、vgs容量和硬盘容量显示不一致解决方法

文章目录pvs中pv显示[unknown]解决方法报错产生情况报错说明解决方法解决方法【无法修复情况,重要!!!】解决方法【正常情况下】正常的剔除一个vg流程【不影响vg已有lv】环境准备强制剔除正常剔除vgs容量和硬盘容量显示不一致解决方…

Mr. Cappuccino的第42杯咖啡——Kubernetes之Pod控制器(一)

Kubernetes之Pod控制器Pod控制器介绍ReplicaSet弹性扩容弹性缩容使用scale命令进行扩容或者缩容更新镜像删除ReplicaSetDeployment弹性扩容与缩容删除Deployment更新镜像重建更新滚动更新版本回退Pod控制器介绍 Pod是Kubernetes集群中能够被创建和管理的最小部署单元。所以需要…

Beats:使用 fingerprint 来连接 Beats/Logstash 和 Elasticsearch

针对带有 HTTPS 访问的 Elasticsearch 集群来说,在我之前的很多文章,我都习惯于使用集群的证书来访问 Elasticsearch。你可以参考我之前的文章 “Elastic Stack 8.0 安装 - 保护你的 Elastic Stack 现在比以往任何时候都简单”。这是一种非常简便的方法。…

一文搞懂 DevOps

前言 DevOps作为一个热门的概念,近年来频频出现在各大技术社区和媒体的文章中,备受行业大咖的追捧,也吸引了很多吃瓜群众的围观。 那么,DevOps是什么呢? 有人说它是一种方法,也有人说它是一种工具&#…

github上传本地文件详细过程

repository 也就是俗称的仓库 声明:后续操作基于win10系统 前提:有一个github账号、电脑安装了git(官方安装地址) 目的: 把图中pdf文件上传到github上的个人仓库中 效果: 温馨提示: git中复制: ctrl insert&#xf…