嵌入式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…...

CTFShow-信息搜集
Web1: 题目描述:开发注释未及时删除 。 打开题目后提示web1:where is flag? ctrlu读取源码。 Web2: 题目描述:js前台拦截 无效操作 打开题目后显示:无法查看源代码 右键无法用,…...

Facebook的虚拟现实功能简介:社交网络的新前沿
在科技飞速发展的今天,虚拟现实(VR)已经从科幻小说中的梦想变成了触手可及的现实。作为全球领先的社交平台,Facebook(现已更名为Meta)正大力推动虚拟现实技术的发展,以重新定义用户的社交体验。…...

Redis embstr 编码
embstr 编码 是 Redis 中一种优化存储小型字符串的编码方式。它是 Redis 内部存储字符串的多种方式之一,特别适用于存储长度不超过 44 字节的小字符串。...

【Elasticsearch系列二】安装 Kibana
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

中国电子学会202403青少年软件编程(Python)等级考试试卷(三级)真题与解析
202403Python 三级真题 一、选择题 1.在 Python 中,hex(2023)的功能是?( ) A.将十进制数 2023 转化为十六进制数 B.将十进制数 2023 转化为八进制数 C.将十六进制数 2023 转化为十进制数 D.将八进制数 2023 转化为十进制数 2.下列表达式的值与其他三个选项不相…...

k8s 资源管理
文章目录 ResourceQuota什么是资源配额定义一个ResourceQuotaResourceQuota的使用 LimitRangeLimitRange的用途示例1:配置默认的requests和limits示例2:配置requests和limits的范围 QoS什么是服务质量保证示例1:实现QoS为Guaranteed的Pod示例…...

演示:基于WPF的自绘的中国地铁轨道控件
一、目的:演示一个基于WPF的自绘的中国地铁轨道控件 二、效果演示 北京地铁 成都地铁 上海地铁 深圳地铁 南京地铁 长春地铁 哈尔滨地铁 武汉地铁 厦门地铁 香港地铁 三、功能 支持平移、缩放等操作 鼠标悬停显示线路信息和站点信息 按表格显示,按纸张…...

设计模式(Design Patterns)
设计模式(Design Patterns)是软件开发人员在软件设计过程中面临的一般性问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式的目的是为了提高代码的可重用性、可维护性、可读性、可靠性以及灵活性。设…...

C++:opencv生成结构元素用于膨胀腐蚀等cv::getStructuringElement
cv::getStructuringElement 是 OpenCV 库中用于生成结构元素的函数。结构元素在形态学操作中(如膨胀、腐蚀、开运算、闭运算等)扮演着关键角色。这个函数可以创建不同形状和尺寸的结构元素,以适应不同的图像处理需求。 函数原型 cv::Mat cv…...

最大余额法,解决百分比计算相加不等于100%(扇形/饼图百分比使用的此算法)
在开发项目的过程中有时候需要进行计算百分比,例如计算饼状图百分比。有时候在计算的过程中常规四舍五入计算会发生所有计算的值相加不等于100%的情况 这是 get_percent_value 函数的 JavaScript 版本: /*** 最大余额法,解决百分比计算相加不…...