嵌入式C语言自我修养:C语言的面向对象编程思想

news2024/9/30 1:38:14

⭐关联知识点: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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2136763.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【YashanDB知识库】单机升级典型问题及应急措施

升级典型问题 官网升级操作指引 离线升级&#xff0c;一般线上操作之前需要照着做一遍&#xff0c;但是由于数据量少、monit进程在测试环境没有启动等原因&#xff0c;一些操作、配置问题在测试过程中不会暴露&#xff0c;在生成操作的时候才暴露&#xff0c;下面3项是比较常见…

你也喜欢“钓鱼“吗?

免责声明:本文仅做分享! 目录 什么是网络钓鱼 流程 攻击手法 0-隐藏自己 1-office宏 创建xxx.dotm 创建xxx.docx 2-RLO 自解压 3-快捷方式lnk 4-邮件伪造 Swaks Gophish 5-网站克隆 setoolkit nginx反向代理 前端页面克隆 6-wifi钓鱼 7-其他 防御 溯源 反…

分布式云化数据库的优缺点分析

分布式云化数据库的优点主要体现在高可用性和容错性、可扩展性、体系结构、数据一致性、成本、升级迭代等方面。同时也存在一些缺点&#xff0c;如通信开销较大、数据的存取结构复杂、数据安全性难以保证、系统复杂性、高并发访问性能问题以及节点故障风险等。以下是对分布式云…

Ruoyi Cloud K8s 部署

参考 https://blog.csdn.net/Equent/article/details/137779505 https://blog.csdn.net/weixin_48711696/article/details/138117392 https://zhuanlan.zhihu.com/p/470647732 https://gitee.com/y_project/RuoYi-Cloud https://blog.csdn.net/morecccc/article/details/1…

hku-mars雷达相机时间同步方案-硬件(MID360与海康MV-CB060-10UMUC-S)

hku-mars雷达相机时间同步方案复线&#xff08;MID360与海康MV-CB060-10UMUC-S&#xff09; 参考 官方指导教程 硬件设备&#xff1a; 硬件同步原理连接方案&#xff1a; 注意&#xff1a; 在我们这个项目中&#xff0c;stm32中GPRMC该数据直接传到Mid360雷达中&#xff0c;由…

筑牢网络安全防线:为数字时代保驾护航

《筑牢网络安全防线&#xff1a;为数字时代保驾护航》 一、网络安全&#xff1a;数字时代的关键课题 网络安全在当今数字时代的重要性愈发凸显。2024 年国家网络安全宣传周以 “网络安全为人民&#xff0c;网络安全靠人民” 为主题&#xff0c;深刻体现了网络安全与每个人息息…

最佳实践 · MySQL 分区表实战指南

引言 在数据量急剧增长的今天&#xff0c;传统的数据库管理方式可能无法有效处理海量数据的存储和查询需求。MySQL 提供了分区表功能&#xff0c;这不仅能够帮助优化性能&#xff0c;还能简化数据管理过程。分区表允许将数据表拆分成多个逻辑上的分区&#xff0c;每个分区可以…

【特点】浅谈大模型的特点

在人工智能(AI)的发展历程中&#xff0c;大模型无疑是一个重要的里程碑。大模型是指利用海量数据&#xff0c;通过先进的算法和技术&#xff0c;训练得到的具有强大预测和决策能力的模型&#xff0c;这类模型具备了强大的语言理解和生成能力&#xff0c;能够完成各种复杂的自然…

【近源攻击】badusb制作

❤️博客主页&#xff1a; iknow181 &#x1f525;系列专栏&#xff1a; 网络安全、 Python、JavaSE、JavaWeb、CCNP &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐评论✍ 在这篇博客中&#xff0c;我将详细介绍如何从零开始使用 ATtiny85 开发板制作一个 BadUSB。通过这个教程…

如何管理付费媒体预算:分配、风险与扩展

对于优先考虑利润率和现金流的企业而言&#xff0c;管理广告预算是一项基本技能。由于人为错误导致的超支几乎总是意味着与客户和利益相关者的艰难对话。这时候&#xff0c;借助光年AI智能平台可以极大程度地降低这种风险&#xff0c;通过AI驱动的全面流量和增长服务&#xff0…

拥塞控制算法为何失效,网络为何难以测量?

紧接着上文 如何测量一个(传输网络)系统的容量 给出的方法&#xff0c;看一下如何测量网络容量&#xff0c;如果真的能测量网络容量&#xff0c;传输算法就好设计了。 先给出答案&#xff0c;很遗憾&#xff0c;根本无法测量&#xff0c;请阅读 why we don’t know how to sim…

前端-CDN的理解及CDN一些使用平台

目录 1.CDN的概念 &#xff08;1&#xff09; 分发服务系统 &#xff08;2&#xff09;均衡负荷系统 &#xff08;3&#xff09;运营管理系统 &#xff08;4&#xff09;缓存系统 &#xff08;5&#xff09;支撑系统 2.CDN的基本工作原理 3.CDN使用缓存资源过程 4.CDN…

CesiumJS+SuperMap3D.js混用实现天际线分析

版本简介&#xff1a; cesium&#xff1a;1.99&#xff1b;Supermap3D&#xff1a;SuperMap iClient JavaScript 11i(2023)&#xff1b; 官方下载文档链家&#xff1a;SuperMap技术资源中心|为您提供全面的在线技术服务 示例参考&#xff1a;support.supermap.com.cn:8090/w…

【Arduino】BNO085 姿态的 3D模型 展示方法(映射到 Unity)

总览 1.arduino 代码和库等… 2.Unity 的部分&#xff0c;创建一个 3D 工程&#xff0c;然后创建一个 cube&#xff0c;绑定一个脚本文件 3.效果预览&#xff1a; 【Arduino】BNO085 姿态的 3D模型 展示方法&#xff08;映射到 Unity&#xff09; 一、Arduino 部分 1.使用的…

《Exploit temporal cues in multi-camera 3D object detection》论文泛读

ReadPaperhttps://readpaper.com/pdf-annotate/note?pdfId4666749915775385601eId2491528568128599808 针对单帧数据含有的信息太少的问题&#xff0c;提出了一种新的方法&#xff0c;BEVDet4D&#xff0c;这种方法可以访问时间线索&#xff0c;并且取得了较好的表现&#xff…

C++类与对象深度解析(一):从抽象到实践的全面入门指南

文章目录 C 类与对象——详细入门指南前言1. 类的定义1.1 类定义的基本格式示例代码解释 1.2 访问限定符示例代码解释 1.3 类域示例代码解释 1.4 成员命名规范常见的命名约定&#xff1a;示例&#xff1a;拓展&#xff1a; 1.5 class与struct的默认访问权限示例&#xff1a; 2.…

Leetcode 每日一题:Decode String

写在前面&#xff1a; 最近求职季找工作忙的焦头烂额&#xff0c;同时这个学期的助教工作也比之前的工时多了一倍&#xff0c;昨天又拖更了真的对不起大家&#xff5e;&#xff5e; 今天我们来看一道稍微轻松一点的题&#xff0c;这道题目来源于 Valid Parenthesis&#xff0…

房产销售系统:SpringBoot技术优化方案

第三章 系统分析 3.1 系统设计目标 房产销售系统主要是为了用户方便对房源信息管理、房源类型管理、房子户型管理、交易订单管理、预约看房管理、评价管理等信息进行查询&#xff0c;也是为了更好的让管理员进行更好存储所有数据信息及快速方便的检索功能&#xff0c;对系统的各…

ESP32聊天机器人之一

想做情感陪伴机器人&#xff0c;看到B站有个项目很有趣&#xff0c;使用一块esp32复刻了B站MeteWu的ESP32大模型聊天项目。 自己做了一些修改&#xff0c;加了一些简单的表情&#xff0c;角色扮演&#xff0c;切换大模型和温湿度传感器等功能。可以用于玩具&#xff0c;聊天机…

第307题|快速掌握 反常积分敛散性判定的方法|武忠祥老师每日一题

解题思路&#xff1a;先判断这个反常积分的敛散性&#xff0c;再讨论a的取值范围; 判断反常积分的敛散性&#xff0c;我们通常有三个方法&#xff1a; &#xff08;1&#xff09;根据定义&#xff0c;通常在原函数比较好求的情况下&#xff0c;可以根据定义 &#xff08;2&am…