嵌入式C语言自我修养:C语言的面向对象编程思想
⭐关联知识点:C和C++的区别
代码复用与分层思想
什么是代码复用呢?
(1)函数级代码复用:定义一个函数实现某个功能,所有的程序都可以调用这个函数,不用自己再单独实现一遍,函数级的代码复用。
(2)将一些通用的函数打包封装成库,并引出API供程序调用,实现了库级的代码复用;
(3)将一些类似的应用程序抽象成应用骨架,然后进一步迭代成框架,实现框架级的代码复用;
如果从代码复用的角度看操作系统,操作系统其实也是对任务调度、任务间通信等功能实现,并引出API供应用程序调用,相当于实现了操作系统级的代码复用。
通常将要复用的具有某种特定功能的代码封装成一个模块,各个模块之间相互独立,使用的时候可以以模块为单位集成到系统中。随着系统越来越复杂,集成的模块越来越多,模块之间会产生依赖关系。为了便于系统的管理和维护,又开始出现分层思想,可以把一个计算机系统分为应用层、系统层、硬件层。
在Linux内核中,往往包含很多模块和子系统,如文件系统、内存管理子系统、进程调度等。每一个模块或子系统也包含着分层的思想。如Linux文件系统,就包括虚拟文件系统VFS和各种类型的文件系统Ext、Fat、NFS等。底层的磁盘、文件系统、虚拟文件系统及应用层的API读写接口也可以实现分层。
一个系统通过分层设计,各层实现各自的功能,各层之间通过接口通信。每一层都是对其下面一层的封装,并留出API,为上一层提供服务,实现代码复用。
面向对象编程基础
面向过程编程中函数是程序的基本单元,可以把一个问题分解成多个步骤来解决,每
一步或每一个功能都可以使用函数来实现。
在面向对象编程中,对象是程序的基本单元,对象是类的实例化,类则是对客观事物抽象而成的一种数据类型,其内部包括属性和方法。
面向对象编程则侧重于将问题抽象、封装成一个个类,然后通过继承来实现代码复用,面向对象编程一般用于复杂系统的软件分层和架构设计。
Linux内核中的OOP思想:封装
内核中的很多子系统、模块在实现过程中处处体现了面向对象编程思想。
类的C语言模拟实现
C语言中没有class关键字,但是可以使用struct模拟一个类,C++类中的属性类似结构体的各个成员。虽然结构体内部不能像类一样可以直接定义函数,但可以在结构体中内嵌函数指针来模拟类中的方法。
struct animal{int age;int weight;void (*fp)(void);
}
如果一个结构体中需要内嵌多个函数指针,可以把这些函数指针进一步封装到一个结构体内。
struct func operations{void(*fp1)(void);void (*fp2)(void);void (*fp3)(void);void (*fp4)(void);
}
struct animal{int age;int weight;struct func operations fp;
}
通过把函数封装在结构体中,然后嵌入该结构体,可以把一个类的属性和方法都封装在一个结构体里。
如何继承?
struct cat{struct animal *p;struct animal ani;char sex;void (*eat)(void);
}
C语言可以通过在结构体中内嵌另一个结构体或结构体指针来模拟类的继承。
在结构体类型cat里内嵌结构体类型animal,此时结构体cat就相当于模拟了一个子类cat,而结构体animal相当于一个父类。
C语言中,内嵌结构体或内嵌指向结构体的指针,可以看作对“继承”的模拟。
链表的抽象与封装
Linux内核中为了实现对链表操作的代码复用,定义了一个通用的链表及相关操作.
struct list head{struct list head *next, *prev;
}
void INIT LIST HEAD(struct list head *list);
int list empty(const struct list head *head);
void list add(struct list head *new, struct list head *head);
void list del(struct list head *entry);
void list replace(struct list head *old,struct list head *new),
void list move(struct list head *list, struct list head *head);
如果想复用Linux内核中的通用链表及相关操作,就可以通过内嵌结构体来继承list_head的属性和方法。
struct my_list node{int data;struct list head list;
}
设备管理模型
Linux如何管理和维护这些设备的信息呢?
Linux的设备管理模型说起。Linux内核中定义了一个非常重要的结构体类型。
struct kobject{
const char *name;
struct list head entry;
struct kobject *parent;
struct kernfs node *sd;
struct kset *kset;
struct kobj_type *ktype;
struct kref kref;
unsigned int state initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state add uevent sent:1;
unsigned int state_remove_uevent sent:1;
unsigned int uevent suppress:1;
}
所有设备在系统中的树结构,kobject结构体用来表示Linux系统中的一个设备,相同类型的kobject通过其内嵌的list_head链成一个链表,然后使用另外一个结构体kset来指向和管理这个列表。
以后再说。
以字符设备为例,我们可以看到字符设备结构体cdev在内核中的定义。
struct cdev {struct kobject kobj; // 内嵌kobject结构体struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};
在结构类型cdev中,通过内嵌结构体kobject来模拟对基类kobject的继承,字符设备的注册与注销,都可以通过继承基类的kobject_add()/kobject_del()方法来完成。与此同时,字符设备在继承基类的基础上,也完成了自己的扩展:实 现 了 自 己的read/write/open/close接口,并把这些接口以函数指针的形式封装在结构体file_operations中。
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);...
};
不同的字符设备,会根据自己的硬件逻辑实现各自的read()、write()函数,并注册到系统中。当用户程序读写这些字符设备时,通过这些接口,就可以找到对应设备的读写函数,对字符设备进行打开、读写、关闭等各种操作。
总线设备模型
Linux每一个设备都要有一个对应的驱动程序,否则就
无法对这个设备进行读写。每一个字符设备都对应的字符设备驱动程序;每一个块设备都有对应的块设备驱动程序。对于一些总线型的设备,如鼠标、键盘、U盘等USB设备,设备通信是按照USB标准协议进行的。
Linux系统为了实现最大化的驱动代码复用,设计了设备-总线-驱动模型:用总线提供的一些方法来管理设备的插拔信息,所有的设备都挂到总线上,总线会根据设备的类型选择合适的驱动与之匹配。通过这种设计,相同类型的设备可以共享同一个总线驱动,实现了驱动级的代码复用。
总线设备模型相关的3个结构体分别为device、bus、driver,它们可以看成基类kobject的子类。
struct device {struct device *parent; // 父设备struct device_private *p; // 内嵌私有数据struct kobject kobj; // 内嵌kobject结构体const struct device_type *type; // 设备类型struct bus_type *bus; // 总线类型struct device_driver *driver; // 驱动程序void *platform_data;void *driver_data;dev_t devt;u32 id;struct klist_node knode_class;struct class *class;void (*release)(struct device *dev);
};
与字符设备cdev类似,在结构体类型device的定义里,也通过内嵌kobject结构体来完成对基类kobject的继承。但其与字符设备不同之处在于,device结构体内部还内嵌了bus_type和device_driver,用来表示其挂载的总线和与其匹配的设备驱动。
device结构体可以看成一个抽象类,我们无法使用它去创建一个
具体的设备。其他具体的总线型设备,如USB设备、I2C设备等可以通
过内嵌device结构体来完成对device类属性和方法的继承。
struct usb_device {int devnum;char devpath[16];u32 route;enum usb_device_state state;enum usb_device_speed speed;struct usb_tt *tt;int ttport;unsigned int toggle[2];struct usb_device *parent;struct usb_bus *bus;struct usb_host_endpoint ep0;struct device dev; // 内嵌device 结构体...
};
Linux内核中的OOP思想:继承
继承与私有指针
除了内嵌结构体,C语言还可以有其他方法来模拟类的继承,如通过私有指针。我们可以把使用结构体类型定义各个不同的结构体变量,也可以看作继承,各个结构体变量就是子类,然后各个子类通过私有指针扩展各自的属性或方法。这种继承方法主要适用于父类和子类差别不大的场合。
如Linux内核中的网卡设备,不同厂家的网卡、不同速度的网卡,以及相同厂家不同品牌的网卡,它们的读写操作基本上都是一样的,都通过标准的网络协议传输数据,唯一不同的就是不同网卡之间存在一些差异,如I/O寄存器、I/O内存地址、中断号等硬件资源不相同。
遇到可以将各个网卡一些相同的属性抽取出来,构建一个通用的结构体net_device,然后通过一个私有指针,指向每个网卡各自不同的属性和方法,这样可以最大程度地实现代码复用。
struct net_device {char name[IFNAMSIZ]; // 网络设备名称const struct net_device_ops *netdev_ops; // 网络设备操作const struct ethtool_ops *ethool_ops; // Ethtool操作void *ml_priv; // 中间层私有数据struct device dev; // 内嵌device结构体
};
当我们使用该结构体类型定义不同的变量来表示不同型号的网卡设备时,这个私有指针就会指向各个网卡自身扩展的一些属性。
继承与抽象类
含有纯虚函数的类,称之为抽象类。抽象类不能被实例化,实例化也没有意义,如animal类,它只能被子类继承。抽象类的作用,主要就是实现分层:实现抽象层。当父类和子类之间的差别太大时,很难通过继承来实现代码复用,如生物类和狗
类,我们可以在它们之间添加一个animal抽象类。抽象类主要用来管理父类和子类的继承关系,通过分层来提高代码的复用性。
如上面设备模型中的device类,位于kobj类和usb_device类之间,通过分层,
可以更好地实现代码复用。
Linux内核中的OOP思想:多态
可以使用C语言来模拟多态:如果把使用同一个结构体类型定义的不同结构体变量看成这个结构体类型的各个子类,那么在初始化各个结构体变量时,如果基类是抽象类,类成员中包含纯虚函数,则为函数指针成员赋予不同的具体函数,然后通过指针调用各个结构体变量的具体函数即可实现多态。
#include <stdio.h>
// 文件操作结构体
typedef struct file_operation {void(*read)(void); // 读取操作void(*write)(void); // 写入操作
} FileOperation;
// 文件系统结构体
typedef struct file_system {char name[20]; // 文件系统名称FileOperation fops; // 文件操作
} FileSystem;// 扩展文件系统的读操作
void ext_read(void) {printf("ext read...\n");
}
// 扩展文件系统的写操作
void ext_write(void) {printf("ext write...\n");
}
// FAT文件系统的读操作
void fat_read(void) {printf("fat read...\n");
}// FAT文件系统的写操作
void fat_write(void) {printf("fat write...\n");
}
int main(void) {// 初始化扩展文件系统FileSystem ext = {"ext3", {ext_read, ext_write}};// 初始化FAT文件系统FileSystem fat = {"fat32", {fat_read, fat_write}};// 文件系统指针FileSystem *fs_ptr;// 指向扩展文件系统fs_ptr = &ext;fs_ptr->fops.read(); // 调用扩展文件系统的读操作// 指向FAT文件系统fs_ptr = &fat;fs_ptr->fops.read(); // 调用FAT文件系统的读操作return 0;
}
相关文章:

嵌入式C语言自我修养:C语言的面向对象编程思想
⭐关联知识点:C和C的区别 代码复用与分层思想 什么是代码复用呢? (1)函数级代码复用:定义一个函数实现某个功能,所有的程序都可以调用这个函数,不用自己再单独实现一遍,函数级的代…...

行车记录仪格式化了怎么恢复?专业恢复方法分享
行车记录仪作为现代驾驶的必备设备,它忠实记录着行车过程中的点点滴滴,是保障行车安全、处理交通事故的重要依据。然而,有时由于操作失误或其他原因,我们可能会不小心将行车记录仪进行格式化,导致宝贵的录像数据丢失。…...
C++中extern ”c“的理解
c中extern “C“的作用及理解_extern "c-CSDN博客...

红黑树的删除
文章目录 前言一.删除的节点左子树右子树都有二.删除的节点只有左/右子树删除调整操作 三.删除的节点没有孩子1.删除的节点为红色2.删除的节点为黑色1).兄弟节点为黑色(1).兄弟节点至少有一个红色的孩子节点LL型RR型RL型LR型 (2).兄弟节点没有孩子或所有孩子为黑色 2).兄弟节点…...
Vue3+setup实现父子组件单表增删改查写法模板
父组件写法 <el-card><!-- el-card 头部插槽 显示列表名和新增按钮 --><template #header><div class"table-header-container"><i class"fas fa-th" />角色列表(100)<span style"flex-grow…...

jmeter 录制APP脚本
一、手机 1、修改网络 代理选择手动→填写服务器主机名(电脑IP,如:192.1xx.x.xx)→服务器端口(任意未被占用端口,如:8888) 2、安装证书 手机浏览器访问服务器主机名:服务器端口&a…...

C++类与对象深度解析(一):从抽象到实践的全面入门指南
文章目录 C 类与对象——详细入门指南前言1. 类的定义1.1 类定义的基本格式示例代码解释 1.2 访问限定符示例代码解释 1.3 类域示例代码解释 1.4 成员命名规范常见的命名约定:示例:拓展: 1.5 class与struct的默认访问权限示例: 2.…...
docker拉取 jdk 8
docker pull openjdk:8docker run -d -it --name java-8 openjdk:8docker run -d -it --name java-8 openjdk:8 –name java-8 容器名,自定义的 openjdk:8 镜像名:标签名 , 使用 docker images 查看 2、查看已运行的容器实例: doc…...
机器学习VS深度学习
机器学习(Machine Learning, ML)和深度学习(Deep Learning, DL)是人工智能(AI)的两个子领域,它们有许多相似之处,但在技术实现和应用范围上也有显著区别。下面从几个方面对两者进行区…...

基于vue框架的宠物交流平台1n2n3(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:会员,宠物信息,宠物类型,团队信息,申请领养,团队申请,领养宠物 开题报告内容 基于Vue框架的宠物交流平台开题报告 一、项目背景 随着现代生活节奏的加快与人们情感需求的日益增长,宠物已成为众多家庭不可或缺的重要成员。…...
Rust 所有权 借用与引用
文章目录 发现宝藏1. 所有权(Ownership)2. 引用(References)2.1 不可变引用2.2 可变引用2.3 引用的规则 3. 悬垂引用(Dangling References)4. 借用(Borrowing)结论 发现宝藏 前些天…...
构建智能电商新生态:深度解析京东商品详情API的力量
在当今数字化浪潮中,智能电商系统已成为推动零售业转型升级的重要引擎。作为电商行业的领军者之一,京东凭借其庞大的商品数据库和先进的技术架构,为开发者与商家提供了丰富的API接口,其中商品详情API无疑是构建智能电商系统的关键…...

Golang | Leetcode Golang题解之第398题随机数索引
题目: 题解: type Solution []intfunc Constructor(nums []int) Solution {return nums }func (nums Solution) Pick(target int) (ans int) {cnt : 0for i, num : range nums {if num target {cnt // 第 cnt 次遇到 targetif rand.Intn(cnt) 0 {ans …...
使用注意力机制可以让你的模型更加灵活,但是需要额外的计算资源。rnn lstm bilstm attension
确实,使用注意力机制可以使模型更加灵活,但也确实需要额外的计算资源。注意力机制允许模型在处理序列数据时,能够动态地关注不同位置的重要性,从而更好地捕捉长依赖关系。下面是一个简单的注意力机制实现示例,可以帮助…...
git命令大全
简介:个人学习分享,如有错误,欢迎批评指正 一、Git操作流程 1、代码提交和同步代码 第零步: 工作区与仓库保持一致第一步: 文件增删改,变为已修改状态第二步: git add ,变为已暂存状态 $ git status $ git add --al…...

【数据仓库】数据仓库常见的数据模型——范式模型
目录 一、范式 1、第一范式 2、第二范式 3、第三范式 4、进一步范式化:BCNF、4NF 和 5NF 简介 (1)Boyce-Codd 范式(BCNF) (2)第四范式(4NF) (5&#x…...

【LeetCode每日一题】——LCR 078.合并 K 个升序链表
文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目注意】六【题目示例】七【题目提示】八【解题思路】九【时间频度】十【代码实现】十一【提交结果】 一【题目类别】 优先队列 二【题目难度】 困难 三【题目编号】 LCR 078.合并 K 个升序链表 …...

代码随想录算法训练营第五十九天 | dijkstra(堆优化版)精讲
目录 dijkstra(堆优化版)精讲 思路 堆优化细节 方法一: 最小堆优化 dijkstra(堆优化版)精讲 题目链接:卡码网:47. 参加科学大会 文章讲解:代码随想录 小明是一位科学家&#x…...

go语言后端开发学习(七)——如何在gin框架中集成限流中间件
一.什么是限流 限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。 我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为8万人;沙河地铁站早高峰通过站外排队逐一放行的…...

SpringBoot2:web开发常用功能实现及原理解析-整合EasyExcel实现Excel导入导出功能
1、工程包结构 主要是这5个Java类 2、导入EasyExcel包 这里同时贴出其他相关springboot的基础包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><depend…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...