嵌入式C语言中if/else如何优化详解
观点一(灵剑):
前期迭代懒得优化,来一个需求,加一个if,久而久之,就串成了一座金字塔。
当代码已经复杂到难以维护的程度之后,只能狠下心重构优化。那,有什么方案可以优雅的优化掉这些多余的if/else?
1. 提前 return
这是判断条件取反的做法,代码在逻辑表达上会更清晰,看下面代码:
if (condition)
{// do something }
else
{return xxx; }
其实,每次看到上面这种代码,我都心里抓痒,完全可以先判断!condition
,干掉 else。
if (!condition)
{return xxx;} // do something
2. 策略模式
有这么一种场景,根据不同的参数走不同的逻辑,其实这种场景很常见。最一般的实现:
if (strategy.equals("fast"))
{// 快速执行 }
else if (strategy.equals("normal"))
{// 正常执行 }
else if (strategy.equals("smooth"))
{// 平滑执行 }
else if (strategy.equals("slow"))
{// 慢慢执行 }
看上面代码,有4种策略,有两种优化方案。
2.1 多态
interface Strategy
{void run() throws Exception; }class FastStrategy implements Strategy
{@Overridevoid run() throws Exception
{// 快速执行逻辑} }class NormalStrategy implements Strategy
{@Overridevoid run() throws Exception
{// 正常执行逻辑} }class SmoothStrategy implements Strategy
{@Overridevoid run() throws Exception
{// 平滑执行逻辑} }class SlowStrategy implements Strategy
{@Overridevoid run() throws Exception
{// 慢速执行逻辑} }
具体策略对象存放在一个Map中,优化后的实现
Strategy strategy = map.get(param);
strategy.run();
上面这种优化方案有一个弊端,为了能够快速拿到对应的策略实现,需要map对象来保存策略,当添加一个新策略的时候,还需要手动添加到map中,容易被忽略。
2.2 枚举
发现很多同学不知道在枚举中可以定义方法,这里定义一个表示状态的枚举,另外可以实现一个run方法。
public enum Status
{NEW(0)
{@Overridevoid run()
{//do something }},RUNNABLE(1)
{@Overridevoid run()
{//do something }};public int statusCode;abstract void run();Status(int statusCode)
{this.statusCode = statusCode;} }
重新定义策略枚举
public enum Strategy {FAST {@Overridevoid run() {//do something }},NORMAL {@Overridevoid run() {//do something }},SMOOTH {@Overridevoid run() {//do something }},SLOW {@Overridevoid run() {//do something }};abstract void run();
}
通过枚举优化之后的代码如下
Strategy strategy = Strategy.valueOf(param);
strategy.run();
3. 学会使用 Optional
Optional主要用于非空判断,由于是jdk8新特性,所以使用的不是特别多,但是用起来真的爽。
使用之前:
if (user == null)
{//do action 1 }
else
{//do action2 }
如果登录用户为空,执行action1,否则执行action 2,使用Optional优化之后,让非空校验更加优雅,间接的减少if操作
Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);
4. 数组小技巧
来自google解释,这是一种编程模式,叫做表驱动法,本质是从表里查询信息来代替逻辑语句,比如有这么一个场景,通过月份来获取当月的天数,仅作为案例演示,数据并不严谨。
一般的实现:
int getDays(int month){if (month == 1) return 31;if (month == 2) return 29;if (month == 3) return 31;if (month == 4) return 30;if (month == 5) return 31;if (month == 6) return 30;if (month == 7) return 31;if (month == 8) return 31;if (month == 9) return 30;if (month == 10) return 31;if (month == 11) return 30;if (month == 12) return 31;
}
优化后的代码
int monthDays[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int getDays(int month){return monthDays[--month];
}
结束
if else 作为每种编程语言都不可或缺的条件语句,在编程时会大量的用到。一般建议嵌套不要超过三层,如果一段代码存在过多的if else嵌套,代码的可读性就会急速下降,后期维护难度也大大提高。
观点二(IT技术控):
不要去过度关注 if/else 的层数,而要关注接口语义是否足够清晰;单纯减少if/else的层数,然后拆出一堆do_logic1, do_logic2…这样的接口是毫无帮助的。
任何一个接口的执行过程都可以表示为:输入 + 内部状态 -> 输出这样的形式,我们分以下几种情况来讨论:
输入、内部状态、输出都很简单,但中间逻辑复杂。比如说一个精心优化过的数值计算程序,可能需要根据输入在不同的取值范围采取不同的策略,还有很多逻辑用来处理会引发问题(比如除0)的边界值,这种情况下 if/else 数量多是难以避免的。
根据步骤拆分出一些内部方法有一定帮助,但也不能完全解决问题。
这种情况下最好的做法是写一篇详细的文档,从最原始的数学模型开始,然后表明什么情况下采取什么样的计算策略,策略如何推导,知道得到代码中使用的具体形式,然后给整个方法加上注释附上文档地址,并且在每个分支的地方加上注释指明对应到文档中哪个公式。
这种情况下虽然方法很复杂,但是语义是清晰的,如果不修改实现的话理解语义就行了,如果要修改实现那么需要参考对照文档中的公式。
输入过于复杂,比如输入带有一堆不同的参数,或者有各种奇怪的flag,每个flag有不同作用。
这种情况下首先需要提高接口的抽象层次:如果接口有多个不同作用,需要拆分成不同接口;如果接口内部根据不同参数进不同分支,需要将这些参数和对应分支包在Adapter里,使用参数的地方改写成Adapter的接口,根据传入的Adapter类型不同进入不同的实现;如果接口内部有复杂的参数转换关系,需要改写成查找表。
这种情况下的主要问题是接口本身抽象的有问题,有更清晰的抽象之后,实现也自然没有那么多if/else了。
输出过于复杂,为了省事一个过程计算出了太多东西,又为了性能加了一堆flag控制是否计算之类。这种情况下需要果断将方法拆分成多个不同方法,每个方法只返回自己需要的内容。
如果不同计算之间有共用的内部结果呢?如果这个内部结果计算并不形成瓶颈,只要提取出内部方法然后在不同过程中分别调用即可;如果希望避免重复计算,可以增加一个额外的 cache 对象作为参数,cache内容对用户不透明,用户只保证相同输入使用同一个cache对象即可,在计算中将中间结果保存到cache中,下次计算前先检查有没有已经得到的结果,就可以避免重复计算了。
内部状态过于复杂。首先检查状态设置的是否合理,是不是有一些本来应该作为输入参数的东西被放到了内部状态中(比如用来隐式地在两个不同方法调用之间传递参数)?
其次,这些状态分别控制哪些方面,是否可以分组然后实现到不同的 StateManager里面?
第三,画出状态转移图,尝试将内部状态分成单层分支,然后分别实现到on_xxx_stat e这样的方法里面,然后通过单层的 switch 或者查找表来调用。
其实通常需要优化的都是整体接口抽象,而不是单个接口的实现,单个接口实现不清晰通常是因为接口实现和需求不同构造成的。
相关文章:

嵌入式C语言中if/else如何优化详解
观点一(灵剑): 前期迭代懒得优化,来一个需求,加一个if,久而久之,就串成了一座金字塔。 当代码已经复杂到难以维护的程度之后,只能狠下心重构优化。那,有什么方案可以优雅…...

【LSTM】读取时间序列数据 | 时间序列数据的小批量划分方法
由于序列数据本质上是连续的,因此我们在处理数据时需要解决这个问题。当序列过长而不能被模型一次性全部处理时,我们希望能拆分这样的序列以便模型方便读取。 Q:怎样随机生成一个具有n个时间步的mini batch的特征和标签? A&…...

K8s in Action 阅读笔记——【12】Securing the Kubernetes API server
K8s in Action 阅读笔记——【12】Securing the Kubernetes API server 12.1 Understanding authentication 在上一章中,我们提到API服务器可以配置一个或多个认证插件(授权插件也是同样的情况)。当API服务器接收到一个请求时,它…...

爆肝整理,3个月从功能进阶自动化测试,一跃成测试卷王...
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 首先先了解自动化…...
人生这场概率游戏,怎么玩
只会标准答案,是不可救药的愚蠢 那么为了便于理解,我用一些典型的案例来讲解,什么是概率游戏,以及这个游戏,应该怎么玩。 比如典型的相亲,婚恋。人生大事,用标准答案来说,你的意中人…...

Redis笔记
缓存过期时间很重要!redis是单线程的 对于内存过多的3中方案: 惰性删除: 在定时删除的基础上,对于已经过期了的数据,redis的随机选择算法一直没有选中这个数据,所以导致它就一直没被删除,但是…...
centos 安装supervisor并运行网站
前言 之前一直用宝塔的**进程守护管理器【Supervisor】**来启动一些项目,如ThinkPHP、Hyperf的项目,或laravel的一些命令。如果不用宝塔怎么办呢? 一、简介[supervisor] [Supervisor] 是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具,不支…...
Hadoop面试题十道
问题 1:Hadoop是什么? 答案:Hadoop是一个开源的分布式计算框架,用于处理大规模数据集的存储和处理。它基于Google的MapReduce和Google文件系统(GFS)的思想,旨在解决大数据量的处理和分析问题。…...

使用Docker-Compose对Docker容器集群快速编排
目录 一、Docker-Compose1、Docker-Compose使用场景2、Docker-Compose简介3、Docker-Compose安装部署4、YAML 文件格式及编写注意事项5、Docker Compose配置常用字段6、Docker Compose 常用命令7、Docker Compose 文件结构8、docker Compose撰写nginx 镜像9、docker Compose撰写…...

React-Redux 对Todolist修改
在单独使用redux的时候 需要手动订阅store里面 感觉特别麻烦 不错的是react有一个组件可以帮我们解决这个问题, 那就是react-redux。 react-redux提供了Provider 和 connent给我们使用。 先说一下几个重点知道的知识 Provider 就是用来提供store里面的状态 自动getState()co…...
初识微信小程序
新建小程序 创建一个新的微信小程序项目: 打开微信开发者工具,点击“新建项目”。 在弹出的窗口中,填写小程序的 AppID、项目名称和项目目录等信息。 点击“确定”按钮,等待微信开发者工具自动下载并安装所需的依赖库和框架。 …...
我们该如何入门编程呢
提醒:以下内容仅做参考,可自行发散。在发布作品前,请把不需要的内容删掉。 随着信息技术的快速发展,编程已经成为一个越来越重要的技能。那么,我们该如何入门编程呢?选择编程语言:选择一种编程…...
App 软件开发《判断6》试卷及答案
App 软件开发《判断6》试卷及答案 文章目录 App 软件开发《判断6》试卷及答案判断题(对的打“√”,错的打“”;共0分)1.”ionic resources --icon"命令用于生成适应不同分辨率的App图标所应用的图片。(✔)2&#…...
MVC工作原理
MVC工作原理 有视图的情况 1.客户端(浏览器)发起请求,DispatcherServlet拦截请求。 2.DispatcherServlet根据请求信息调用HandlerMapping。HandlerMapping根据uri去匹配查询能处理的Handler(也就是我们所说的Controller&#x…...
使用 Redis 统计网站 UV 的方法
使用 Redis 统计网站 UV 的方法(概率算法) 文章目录 前言思路HyperLogLog 使用 Redis 命令操作使用 Java 代码操作 HyperLogLog 实现原理及特点使用 Java 实现 HyperLogLog小结 前言 网站 UV 就是指网站的独立用户访问量Unique Visitor,即相同用户的多次访问需要…...

黑客工具软件大全
黑客工具软件大全100套 给大家准备了全套网络安全梓料,有web安全,还有渗透测试等等内容,还包含电子书、面试题、pdf文档、视频以及相关的网络安全笔记 👇👇👇 《黑客&网络安全入门&进阶学习包》 &a…...

uniapp主题切换功能的第二种实现方式(scss变量+require)
在上一篇 “uniapp主题切换功能的第一种实现方式(scss变量vuex)” 中介绍了第一种如何切换主题,但我们总结出一些不好的地方,例如扩展性不强,维护起来也困难等等,那么接下我再给大家介绍另外一种切换主题的…...

# 蓝牙音频相关知识
蓝牙音频相关知识 文章目录 蓝牙音频相关知识1 音频源2 蓝牙音频编解码器3 一些标准4 蓝牙音频其他相关知识4.1 蓝牙版本4.2 ANC(主动降噪)4.3 音响相关参数4.4 音质评价4.5 HI-Fi声音特点4.6 耳机线材4.7 耳机分类4.8 IP防尘防水等级4.9 噪音与量化噪音…...

【AI作画】使用DiffusionBee with stable-diffusion在mac M1平台玩AI作画
DiffusionBee是一个完全免费、离线的工具。它简洁易用,你只需输入一些标签或文本描述,它就能生成艺术图像。 DiffusionBee下载地址 运行DiffusionBee的硬性要求:MacOS系统版本必须在12.3及以上 DBe安装完成后,去C站挑选自己喜欢…...
2 STM32库函数 之 通用同步异步收发器(USART、串口)所有函数的介绍及使用
2 STM32库函数 之 通用同步异步收发器(USART、串口)所有函数的介绍及使用 前言一、USART固件库函数预览二、USART固件库函数具体介绍2.1 库函数 USART_DeInit2.2 库函数 USART_Init2.2.1 USART_InitTypeDef structure2.2.2 USART_InitTypeDef 成员 USART…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...