书摘:C 嵌入式系统设计模式 02
本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。
本系列描述我对书中内容的理解。
结构化编程将软件组织成两个截然不同的方面:数据和行为。面向对象的方法将两者结合起来,让紧密耦合的元素更内聚,并提高内容的封装。C 是结构化语言,但它可以用于开发面向对象的嵌入式系统。
这里提到了 结构化编程 和 面向对象编程 ,C 语言支持结构化编程, C++ 则支持面向对象编程。其实到目前为止,还有第 3 种编程范式,那就是 函数式编程 。这三种编程范式至今都在广泛使用,它们诞生至今已经有相当长的时间了。正如我在《关于编程》中提到的:
- 1958 年
John Mccarthy发明了 LISP 语言,函数式编程范式诞生 - 1966 年
Ole Johan Dahl和Kriste Nygaard的论文开创了面向对象编程范式 - 1968 年
Edsger Wybe Dijkstra论证了goto语句的危害,结构化编程范式诞生
经过了几十年的发展,今天的编程范式与过去完全一样,也是结构化编程 范式、 面向对象编程 范式和 函数式编程 范式,再没有出现新的编程范式。
结构化编程 的核心理念是将复杂的程序问题分解为更小、更容易管理的子问题。“结构化”在这里意味着用只用有限的几种结构(顺序、分支、循环)来构建程序,避免使用 goto 等可能导致程序流程难以跟踪和控制的结构。
结构化编程是由 Edsger Wybe Dijkstra 于 1968 年提出的。Dijkstra 于1930 年出生于荷兰,他很早就发现编程是一件难度很大的工作。一段程序无论复杂与否,都包含了很多细节信息,这远超一个程序员的认知能力范围。而即便是一个小细节的错误,也会造成整个程序出错。他想用数学来证明程序是正确的,而且他也成功了。
Dijkstra 在研究的过程中发现了一个问题:不加限制的 goto 语句导致模块无法拆分成更小的、可被证明的单元,如果禁用 goto,只使用顺序结构、分支结构(if-then-else)和循环结构(do–while)编程,那么程序就可以被数学所证明。于是,结构化编程诞生了。
1968 年,Dijkstra 写了那封著名的,后来发表于 CACM ,标题为《Go To Statement Considered Harmful》的文章。“goto 是有害的”这个观点,这在当时引起了很多程序员的不满。在当时,使用汇编跳转是家常便饭,因此 Dijkstra 的观点掀起了一场长达 10 年的辩论。然而,最后辩论还是平息了,因为事实证明, Dijkstra 是对的。
Dijkstra 的工作是证明一段程序在数学上是正确的,这在实际操作中可太难了。工程界采用的是方法是证明这个程序是错误的,证伪可比证实简单多了,只要能找到一个 BUG,证明就结束了。如果这段程序经过一定的努力无法证伪,我们则认为它在当下是足够正确的。值得注意的是,只有在可证明的程序上,才可以使用这种方法证伪。如果某段程序采用了不加限制的 goto 语句,那么再多的测试也不能证明其正确性。
结构化编程范式促使我们将功能递归的分解为一系列可证明的小函数,然后再编写相关的测试来试图证明这些函数是错的。如果这些测试无法证伪这些函数,那么我们就可以认为这些函数是足够正确的,进而推导整个程序是正确的。
作者说“结构化编程将软件组织成两个截然不同的方面:数据和行为。面向对象的方法将两者结合起来,让紧密耦合的元素更内聚,并提高内容的封装。”这句话我并不是很理解,在我的认识里,无论结构化编程还是面向对象编程,优秀的程序员们总是首先关注数据结构,不仅要考虑如何表示数据,还要考虑如何使用数据。因此无论用什么编程范式,数据和行为都是天然结合起来的,不存在结构化编程将软件组织成两个截然不同的方面。不同的是,结构化编程语言在 文件 中实现数据和行为的结合,面向对象编程在 类 中实现数据和行为的结合,从本质上讲,这不过是形式上的不同,思想上,它们是一致的。
然后是关于封装。封装就是隐藏不必要的细节,包括数据结构和实现细节。一个最好的例子是文件系统。文件系统的实现非常复杂,有一个很大的数据结构,但这些都被精心隐藏起来了,你只需要操作几个简单的函数 open 、read、 write,就可以完成大部分文件操作。模块的头文件就是模块的接口,这里面可以只有 API 函数声明,没有任何数据结构的声明,但 C++ 就办不到,因为技术的限制,C++ 的头文件中必须包含类的声明,这样类中的所有元素都暴露在外了。如果只是谈封装,C 语言是要好于 C++ 的。
“C 可以用于开发面向对象的嵌入式系统”,这是书中的一个结论。我认同这个结论,不是说要使用复杂的宏定义封装 C 语言,模仿 C++ 的语法,而是要深刻理解面向对象的思想,站在更高的层次上编程,首先要理解的,也是最重要的,是 多态 。
多态 是指多种形态,就是指同一个方法的行为随上下文而异。归根结底,多态不过是函数指针的一种应用。举一个例子:某设备具有多种运行模式:普通模式、访客模式、特权模式…,对于每种模式,显示方式不同、控制逻辑不同、上传的数据也不同。遇到这样的情况,你会下意识用 switch - case 语句解决吗?如果你使用 switch - case 语句解决,那么在显示、控制、上传这些地方,都会有一个 switch - case 语句,不断地重复各种模式。这还没完,当你新增一个模式时,还需要在所有关于模式的 switch 语句处增加 case 语句,来处理新的模式。可以用函数指针代替 switch - case 语句。
首先,我们抽象出一个接口,接口就是一系列函数指针,这些指针规定了每种模式都需要操作的函数原型。然后每种模式都自己实现这些函数。最后将实现的函数动态的绑定到函数指针上,对使用接口的代码而言,它们再也不需要 switch - case 语句区分模式了,它们甚至无需关注模式,因为接口对此做了隐藏。就像在你的电脑上,文件系统是一个接口,我们只管用 write 函数存储参数,而不用理会存储介质,电脑上装的是硬盘就存硬盘上,装的是刻录机就存光盘上。
当理解了面向对象编程思想,理解了 SOLID 设计模式,我写代码的原则变成了只有以下 2 条:
- 用测试驱动开发编写最简洁的代码。
- 编写设备无关的代码
怎么算简洁?一个函数,你看上一遍,能拍着胸脯对自己说,它绝不会有 BUG ,这就是简洁。与之相反的,有一个函数,你看上半天,最后只能说,没有看到明显的 BUG,这就是复杂。
什么是设备无关的代码?不依赖具体硬件、不依赖底层实现,就像在应用层存储数据不必知道存哪种介质,只有这样,你才能更方便的更换存储介质。与之相反的,应用层中的函数一路调用下去,可以看到寄存器,那么应用层就跟硬件深度绑定了,任何硬件的改动,都要更改大量的代码。
相关文章:
书摘:C 嵌入式系统设计模式 02
本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。 本系列描述我对书中内容的理解。 结构化编程将软件组织成两个截然不同的…...
排序算法基本原理及实现1
📑打牌 : da pai ge的个人主页 🌤️个人专栏 : da pai ge的博客专栏 ☁️宝剑锋从磨砺出,梅花香自苦寒来 📑插入排序 Ǵ…...
Unity 轨道展示系统(DollyMotion)
DollyMotion 🍱功能展示🥙使用💡设置路径点💡触发点位切换💡动态更新路径点💡事件触发💡设置路径💡设置移动方案固定速度方向最近路径方向 💡设置移动速度曲线 传送门 &a…...
优维低代码实践:搜索功能
优维低代码技术专栏,是一个全新的、技术为主的专栏,由优维技术委员会成员执笔,基于优维7年低代码技术研发及运维成果,主要介绍低代码相关的技术原理及架构逻辑,目的是给广大运维人提供一个技术交流与学习的平台。 优维…...
C# ReadOnlyRef Out
C# ReadOnly ReadOnly先看两种情况1.值类型2.引用类型 结论 Ref Out ReadOnly官方文档 ReadOnly 先看两种情况 1.值类型 当数据是值类型时,标记为Readonly时,如果再次设置值,会提示报错,无法分配到只读字段 public class A {pri…...
linux 服务 下 redis 安装和 启动
官网下载 https://redis.io/download/ 安装步骤: 1.安装redis 所需要的依赖 yum install -y gcc tcl2.上传安装包并解压,下载安装包,上传到/usr/local/src目录,解压 tar -zxvf redis-7.2.3.tat.gz进入安装目录,运行…...
ECharts与Excel的结合实战
引言:本文是一篇ECharts和Excel实战的记录。将Excel与ECharts产生火花,从Excel读取数据然后在ECharts上展示。 1.柱状图前端代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title…...
UDP的特点及应用场景
目录 UDP特点 应用场景 总结 User Datagram Protocol(UDP,用户数据报协议)是互联网协议套件中的一种传输层协议。与TCP不同,UDP是一种无连接的、不可靠的协议。 UDP特点 要知道UDP可以用来做什么,首先我们要知道它…...
Python开发——工具篇 Pycharm的相关配置,Python相关操作 持续更新
前言 本篇博客是python开发的工具篇相关,介绍pycharm的使用和相关配置,收录python的相关操作,比如如何启动jupyter。 目录 前言引出Pycharmpycharm如何不同等级日志显示不同颜色设置不同pycharm的python环境 Python操作如何启动Jupyter 总结…...
【深度学习】卷积神经网络结构组成与解释
卷积神经网络是以卷积层为主的深度网路结构,网络结构包括有卷积层、激活层、BN层、池化层、FC层、损失层等。卷积操作是对图像和滤波矩阵做内积(元素相乘再求和)的操作。 1. 卷积层 常见的卷积操作如下: 卷积操作解释图解标准卷…...
从源码解析Containerd容器启动流程
从源码解析Containerd容器启动流程 本文从源码的角度分析containerd容器启动流程以及相关功能的实现。 本篇containerd版本为v1.7.9。 更多文章访问 https://www.cyisme.top 本文从ctr run命令出发,分析containerd的容器启动流程。 ctr命令 查看文件cmd/ctr/comman…...
引迈-JNPF低代码项目技术栈介绍
从 2014 开始研发低代码前端渲染,到 2018 年开始研发后端低代码数据模型,发布了JNPF开发平台。 谨以此文针对 JNPF-JAVA-Cloud微服务 进行相关技术栈展示: 1. 项目前后端分离 前端采用Vue.js,这是一种流行的前端JavaScript框架&a…...
如何处理枚举类型(下)
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 上一篇我们通过编写MyB…...
wsj0数据集原始文件.wv1.wv2转换成wav文件
文章目录 准备一、获取WSJO数据集二、安装sph2pipe三、转换代码四、结果展示 最近做语音分离实验需要用到wsj0-2mix数据集,但是从李宏毅语音分离教程里面获取的wsj0-2mix只有一部分。从网上获取到了完整的WSJO数据集后,由于原始的语音文件后缀是wv1或…...
Flask Session 登录认证模块
Flask 框架提供了强大的 Session 模块组件,为 Web 应用实现用户注册与登录系统提供了方便的机制。结合 Flask-WTF 表单组件,我们能够轻松地设计出用户友好且具备美观界面的注册和登录页面,使这一功能能够直接应用到我们的项目中。本文将深入探…...
【运维】hive 高可用详解: Hive MetaStore HA、hive server HA原理详解;hive高可用实现
文章目录 一. hive高可用原理说明1. Hive MetaStore HA2. hive server HA 二. hive高可用实现1. 配置2. beeline链接测试3. zookeeper相关操作 一. hive高可用原理说明 1. Hive MetaStore HA Hive元数据存储在MetaStore中,包括表的定义、分区、表的属性等信息。 hi…...
C#开发的OpenRA游戏之属性SelectionDecorations(13)
C#开发的OpenRA游戏之属性SelectionDecorations(13) 在前面分析SelectionDecorations属性类时,会发现它有下面这个属性: public class SelectionDecorations : SelectionDecorationsBase, IRender { readonly Interactable interactable; 它是定义了一个Interactabl…...
接手了一个外包开发的项目,我感觉我的头快要裂开了~
嗨,大家好,我是飘渺。 最近,我和小伙伴一起接手了一个由外包团队开发的微服务项目,这个项目采用了当前流行的Spring Cloud Alibaba微服务架构,并且是基于一个“大名鼎鼎”的微服务开源脚手架(附带着模块代…...
git常规使用方法,常规命令
Git是一种分布式版本控制系统,它可以记录软件的历史版本,并提供了多人协作开发、版本回退等功能。以下是Git的基本使用方法: 安装Git:下载安装包并进行安装,安装完成后在命令行中输入 git --version 进行验证。 初始化…...
【JavaScript】3.3 JavaScript工具和库
文章目录 1. 包管理器2. 构建工具3. 测试框架4. JavaScript 库总结 在你的 JavaScript 开发之旅中,会遇到许多工具和库。这些工具和库可以帮助你更有效地编写和管理代码,提高工作效率。在本章节中,我们将探讨一些常见的 JavaScript 工具和库&…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
