书摘: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 工具和库&…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
