如何写出易维护的嵌入式代码?

发布时间:2023-08-31  

面向对象的C

本文引用地址:

面向对象的语言更接近人的思维方式,而且在很大程度上降低了的复杂性,同时提高了的可读性和可维护性,传统的C同样可以设计出比较易读,易维护,复杂度较低的优美代码,本文将通过一个实际的例子来说明这一点。

基础知识

结构体

除了提供基本数据类型外,还提供给用户自己定制数据类型的能力,那就是结构体,在中,你可以用结构体来表示任何实体。结构体正是面向对象语言中的类的概念的雏形,比如:

typedef struct{ 
    float x; 
    float y; 
 }Point;

定义了一个平面坐标系中的一个点,点中有两个域,x坐标和y坐标。

结构体中的域称为结构体的成员。结构体中的数据类型可以是简单数据类型,也可以是其他的结构体,甚至结构体本身还可以嵌套,比如,一个标准的链表结构可以进行如下定义:

typedef struct node{ 
    void *data;// 数据指针
    int dataLength;// 数据长度
    struct node *next;// 指向下一个节点
 }Node;

可以看到,结构体node中的next指针的类型又是node类型。

函数指针

指针是的灵魂,是C比其他语言更灵活,更强大的地方。所以学习C语言必须很好的掌握指针。函数指针,即指向函数在内存映射中的首地址的指针,通过函数指针,可以将函数作为参数传递给另一个函数,并在适当的时候调用,从而实现异步通信等功能。

比如, UNIX/Linux系统中的信号注册函数,其原型如下:

void (*signal(int signo,void (*func)(int))) (int)

使用的时候,需要自己在外部定义一个信号处理函数(signal handler), 然后使用signal(sigNo, handler)将处理程序注册在进程上,当信号发生时,进程就可以回调信号处理函数。

将函数指针作为结构体的成员

正如前面提到的,结构体的成员可以是简单的数据结构,也可以是其他的结构体,当然,也可以是指针。当将函数指针作为结构体的成员,并且这些函数只用来操作本结构体中的数据时,就可以形成一个独立的实体,这个实体中既有数据,也有对数据的操作,这样自然就可以引出类(class)的概念。

面向对象语言的特性

一般而言,继承,封装和多态被认为是面向对象语言所必须支持的三种特征,也正是通过这三种特征才可以体现出面向对象在哪些方面优于面向过程。

由于语言开发商的宣传或其他的各种原因,使的表面上面向对象的思想要通过语言为载体而得以实现,然而实际上,面向对象是一种软件设计思想,完全是可以与具体实现无关的。

虽然如此,但是不可否认,这些所谓的纯面向对象的语言,在其代码的可读性以及与人的自然思维的匹配方面,比面向过程的语言要好的多。

语言层次的面向对象

我们一般要描述一个对象,一般需要描述这个对象的一些属性,比如盒(box)是一个实体,它有6个面,有颜色,重量,是否为空等属性,并且可以放东西进去,可以取东西出来。

在面向对象的语言中,通常将这样的对象抽象成一个类(class):

class Box{ 
    clolr color; 
    int weight; 
    boolean empty; 
    
    put(something); 
    something get()
 }

对盒子进行操作时,可以做一下动作:

 Box.put(cake); 
 Box.get();// 取到某个东西,从盒子中。

而面向过程的语言中,通常是将实体传递给一个贯穿全局的函数来进行的,同样以Box为例,对Box进行操作时,往往是这样:

 Put(Box, cake);// 将一个蛋糕放到盒子中
 Get(Box);// 从盒子中取出某个东西来

而显然,第一种代码形式更符合常理,所以面向对象的语言大都提供这种语言层面的细节的支持,使得代码的可读性,可理解性大大增加。

C语言,作为一个灵活而简单的语言,我们完全可以通过C提供的简单机制,实现这样的比较优美的代码形式。

C语言的面对对象

如前所说,面向对象是一种软件设计的思想,是语言无关的。在本节中,我举一个链表(list)的例子来说明如何在C语言中的设计出有面向对象风格的代码。

定义接口

接口是面向对象语言中的一个比较重要的概念,接口只对外部承诺实现该接口的实体可以完成什么样的功能,但是不暴露实现的方式。这样的好处是,实现者可以在不接触接口使用者的代码的情况下,对实现进行调整。

我们来看看链表的接口定义:

清单 1.链表的接口定义

#ifndef _ILIST_H 
 #define   _ILIST_H 
 
 // 定义链表中的节点结构
 typedef struct node{ 
    void *data; 
    struct node *next; 
 }Node; 
 
 // 定义链表结构
 typedef struct list{ 
    struct list *_this; 
    Node *head; 
    int size; 
    void (*insert)(void *node);// 函数指针
    void (*drop)(void *node); 
    void (*clear)(); 
    int (*getSize)(); 
    void* (*get)(int index); 
    void (*print)(); 
 }List; 
 
 void insert(void *node)
 void drop(void *node)
 void clear()
 int getSize()
 voidget(int index)
 void print()
 
 #endif   /* _ILIST_H */

IList接口中,可以清晰的看到,对于一个list实体(也就是对象)来说,可以在其上进行insert、drop、clear、getSize、get(index)以及print等操作。

接口的实现

清单 2.构造方法

Node *node = NULL
 List *list = NULL
 
 void insert(void *node)
 void drop(void *node)
 void clear()
 int getSize()
 void print()
 voidget(int index)
 
 List *ListConstruction()
    list = (List*)malloc(sizeof(List)); 
    node = (Node*)malloc(sizeof(Node)); 
    list->head = node; 
    list->insert = insert;// 将 insert 函数实现注册在 list 实体上
    list->drop = drop; 
    list->clear = clear; 
    list->size = 0
    list->getSize = getSize; 
    list->get = get; 
    list->print = print; 
    list->_this = list;// 用 _this 指针将 list 本身保存起来
 
    return (List*)list
 }

需要注意的是此处的_this指针,_this指针可以保证外部对list的操作映射到对_this的操作上,从而使得代码得到简化。

清单 3.插入及删除

// 将一个 node 插入到一个 list 对象上
 void insert(void *node)
    Node *current = (Node*)malloc(sizeof(Node)); 
    
    current->data = node; 
    current->next = list->_this->head->next; 
    list->_this->head->next = current; 
    (list->_this->size)++; 
 } 
 
 // 删除一个指定的节点 node 
 void drop(void *node)
    Node *t = list->_this->head; 
    Node *d = NULL
    int i = 0
    for(i;i < list->_this->size;i++){ 
        d = list->_this->head->next; 
        if(d->data == ((Node*)node)->data){ 
            list->_this->head->next = d->next; 
            free(d); 
            (list->_this->size)--; 
            break
        }else
            list->_this->head = list->_this->head->next; 
        } 
    } 
    list->_this->head = t; 
 }

其他的实现代码可以参看下载部分,这里限于篇幅就不再意义列举出来。

测试

文章来源于:电子产品世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    需要以最高的权限运行,因此成功的代码注入攻击可以完全控制机器以及窃取数据,导致设备发生故障,将其作为其僵尸网络成员或使其永久无法使用。 代码注入的关键方面是: · 该程序从输入通道读取数据 · 该程序将数据视为代码......
    STM32CUBEIDE(2)----生成简单的例程;概述 本章主要讲解通过STM32CUBEMX 生成代码,使用STM32CUBEIDE进行代码编写,需要样片的可以加群申请:615061293......
    如何编写ARM7的启动代码(LPC2119为例);随着生活水平的提高和IT技术的进步,8位处理器的处理能力已经不能满足嵌入式系统的需要了;而16位处理器在性能和成本上都没有很大的突破。并且在8位机......
    经验表明,编写汇编语言会造成误解。误解会导致维护不当,更甚者,可能会使系统到处是bug,一般建议避免使用汇编语言。实际上,现在大多数编译器都能编译出非常高效的代码。采用C语言或C++语言......
    内容。 如何设置读保护    在程序的开头加入“设置读保护”的代码即可,每次运行代码时都检查一下,如果没有开就打开,如果打开了就跳过。其中,设置读保护的代码如下:     上面的代码执行后,使用j......
    使能PCROP保护以及如何导出接口符号供二次开发使用。 你可以编译运行PCROP参考代码。一旦下载到开发板并运行后,扇区2会自动被设置成PCROP保护。你将无法再次下载代码到该扇区,也无法......
    设置读保护和解除读保护? 读保护设置后将不能读出Flash中的内容。 如何设置读保护 在程序的开头加入“设置读保护”的代码即可,每次运行代码时都检查一下,如果没有开就打开,如果打开了就跳过。其中,设置读保护的代码......
    设置读保护和解除读保护? 读保护设置后将不能读出Flash中的内容。 如何设置读保护 在程序的开头加入“设置读保护”的代码即可,每次运行代码时都检查一下,如果没有开就打开,如果打开了就跳过。其中,设置读保护的代码......
    一个简单的项目,你可能在嵌入式项目中找不到任何严肃的应用。该项目工作只是一个示范。 如何编写程序? 代码 是一种物理设备,当按下一个按钮时,在一定范围内随机产生一个数字。在这个项目中,当按下按钮(连接P3.0......
    相关函数 2)STM32如何设置读保护? 我们只需要在程序开头加入“设置读保护”的代码就可以,这样就可以在每次运行代码的时候都检查一下,如果没有开的话就打开,如果开了就跳过。下面是读保护的代码: 当我们在程序的开头执行了上面的代码......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>