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

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

在这里插入图片描述

文章目录

  • 【梅开一度】:观察库函数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日记,有感而发,还望采纳❤

相关文章:

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

文章目录【梅开一度】:观察库函数strcpy()的实现【梅开二度】:模仿实现strcpy()【梅开三度】:优化简练代码【梅开四度】:assert()断言拦截【梅开五度】:const修饰常量指针【梅开六度】:还可以有返回值哦&am…...

家庭理财,轻松记账修改收支记录这样操作

我们在记账的时候难免会出现记错或者想修改的地方,又或者是想将之前太久没有用的记账记录删除掉,今天,小编就教大家如何修改收支记录,一起接着往下看吧! 第一步,运行【晨曦记账本】在软件主界面中&#xff…...

河南工程学院2.17蓝桥杯培训

乘法口诀数列:https://www.acwing.com/problem/content/3466/ 剪绳子:https://www.acwing.com/problem/content/68Sin SinSine之舞:http://lx.lanqiao.cn/problem.page?gpidD5272 数列:https://www.acwing.com/problem/content/…...

【JavaSE】数据类型与变量

JAVA之父:詹姆斯高斯林 (James Gosling) 前言: 大家好,我是程序猿爱打拳。今天我给大家讲解的是Java基础中的数据类型。主要讲解的是各个类型的应用场景以及注意事项。 目录 1.数据类型 2.常量与变量 2.1常量 2.2变…...

生成模型技术发展过程

生成模型生成模型和判别模型的差异生成模型的目标是在给定了数据集D,并且假设这个数据集的底层分布(underlying distribution)是Pdata,我们希望够近似出这个数据分布。如果我们能够学习到一个好的生成模型,我们就能用这个生成模型为下游任务做…...

计算机网络第2章(物理层)学习笔记

❤ 作者主页:欢迎来到我的技术博客😎 ❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~* 🍊 如果文章对您有帮助,记得关注、点赞、收藏、…...

4564: 保留尾部*

描述规定输入的字符串中只包含字母和*号,除了尾部的*号之外,请将字符串中其他*号全部删除。输入输入数据包括一串字符串,只包含字母和*,总长度不超过80。输出输出按要求删除*后的字符串。样例输入*******A*BC*DEF*G****样例输出AB…...

安卓项目搭建grpc环境

本篇文章使用的IDE是Android Studio。这里先吐槽一句,安卓项目搭建grpc环境,不管是引入插件还是引入第三方库,对于版本的要求都极为苛刻,一旦版本不匹配就会报错,所以对于版本的搭配一定要注意。 下面介绍的这个版本搭…...

Flink01: 基本介绍

一、什么是Flink 1. Flink是一个开源的分布式,高性能,高可用,准确的流处理框架 (1)分布式:表示flink程序可以运行在很多台机器上, (2)高性能:表示Flink处理性…...

设计模式之单例模式

文章の目录一、什么是单例模式二、如何实现单例模式1、利用JavaScript中的全局对象2、静态成员改造参考写在最后一、什么是单例模式 单例模式也称为单体模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。 举个栗子:一个班级只有一…...

[oeasy]python0086_ASCII_出现背景_1963年_DEC_PDP系列主机_VT系列终端

编码进化 回忆上次内容 上次 回顾了 字符编码的新陈代谢 ibm 曾经的EBCDIC 由于 字符不连续导致 后续 出现无数问题 随着 网络的发展 数据交换的 需要原来的小隐患现在 产生了 巨大问题 Bemer 联合各方巨头 想要推出 字符连续的编码集 这新编码集 具体长什么样 呢&#xff1…...

基于FFmpeg实现的无声音屏幕录制

UI自动化测试时,有时需要进行录屏操作,这时我们是不需要声音的,我们可以通过FFmpeg进行简单的录制工作。 以下是在windows10环境下,基于FFmpeg实现的简单录制: Ffmpeg简介: 功能:有非常强大的…...

【项目精选】基于JSP物流信息网(论文+源码+视频)

点击下载源码 近年来,随着时代的进步,社会随之不断发展,经济也快速发展起来了,人民的消费水平在不断地提高,平常的实体店消费已经不能满足人们的需求;在者,互联网技术的不断发展也为电子商务的兴…...

linux异步IO编程实例分析

在Direct IO模式下,异步是非常有必要的(因为绕过了pagecache,直接和磁盘交互)。linux Native AIO正是基于这种场景设计的,具体的介绍见:KernelAsynchronousI/O (AIO)SupportforLinux。下面我们就来分析一下…...

日常英语口语练习-情景交际场景25(三)

登山踏青m: hey Carol, what are you doing this weekend?o: im going hiking /haikiŋ/登山with my husband and our hiking clubm: you have a hiking culb?o: yes, we do, we have 30 to 40 people of all ages and skill levelsm: thats great, do you gus do…...

Qt 工程师进阶技术23种设计模式

Qt 工程师进阶技术23种设计模式【1】23种设计模式【1】23种设计模式 设计模式是解决特定问题的一系列套路,这套方案提高代码可复用性、可读性、稳健性、可维护性及安全性。 23种设计模式可分为三类:结构型模式(侧重类与对象之间的组合)、行为型模式(侧重…...

Redis 强化

(Redis入门使用查看)https://blog.csdn.net/weixin_73849581/article/details/128390152?spm1001.2014.3001.5501缓存使用原则什么时候,什么样的数据能够保存在Redis中?1.数据量不能太大2.使用越频繁,Redis保存这个数据越值得3.保存在Redis中的数据一般不会是数据库中频繁修改…...

华为OD机试题 - 众数和中位数(JavaScript)

最近更新的博客 华为OD机试题 - 任务总执行时长(JavaScript) 华为OD机试题 - 开放日活动(JavaScript) 华为OD机试 - 最近的点 | 备考思路,刷题要点,答疑 【新解法】 华为OD机试题 - 最小步骤数(JavaScript) 华为OD机试题 - 任务混部(JavaScript) 华为OD机试题 - N 进…...

Go: expected pseudo-register found R13 error

报错描述: 启动Go项目时,报错: ../../../.go/pkg/mod/github.com/choleraehyq/pidv0.0.10/pid_go1.5_amd64.s:28: expected pseudo-register; found R13 原因分析: github.com/choleraehyq/pid这个依赖包的版本太低,需…...

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

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

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

Python如何给视频添加音频和字幕

在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

【Java多线程从青铜到王者】单例设计模式(八)

wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本,sleep也是可以指定时间的,也就是说时间一到就会解除阻塞,继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒),wait能被notify提前唤醒&#xf…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...

Linux中INADDR_ANY详解

在Linux网络编程中&#xff0c;INADDR_ANY 是一个特殊的IPv4地址常量&#xff08;定义在 <netinet/in.h> 头文件中&#xff09;&#xff0c;用于表示绑定到所有可用网络接口的地址。它是服务器程序中的常见用法&#xff0c;允许套接字监听所有本地IP地址上的连接请求。 关…...

Unity-ECS详解

今天我们来了解Unity最先进的技术——ECS架构&#xff08;EntityComponentSystem&#xff09;。 Unity官方下有源码&#xff0c;我们下载源码后来学习。 ECS 与OOP&#xff08;Object-Oriented Programming&#xff09;对应&#xff0c;ECS是一种完全不同的编程范式与数据架构…...