2级菜鸟 2026-03-21 15:32 采纳率: 33.3%
浏览 6

嵌入式C语言面向对象实现,我该如何学习?

问题

我入职了嵌入式软件工程师,看到公司的项目代码是用C语言以面向对象的思维编码的,我觉得这很好,很想学习。因此,在这里询问我该如何学习,应该去看哪些资料?顺便也想问问嵌入式中用C语言以面向对象的思维编码是否合适,在什么情况下建议使用?

以下代码是我看过公司代码后,自己尝试进行了思考,也尝试简单用C语言以面向对象的思维实现一个单例模式。

代码

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "cmsis_armcc.h"

/* ------------------------------------------------------------------------------------------------------------------ */

/**
 * 标记宏定义,用于修饰函数、变量、常量、结构体成员等等。部分宏标记具有关键字修饰功能,注意使用。
 * 
 */

// 公共可读写(外部可访问,可修改)
#define PUBLIC              __attribute__((annotate("public")))

// 公共只读(外部可访问,不可修改)
#define PUBLIC_CONST        __attribute__((annotate("public const")))

// 内部使用(外部禁止访问),主要用于标记结构体成员,不可被外部修改与访问
#define INTERNAL             __attribute__((annotate("internal")))

// 文件静态私有(真正的私有)
#define PRIVATE_STATIC       static __attribute__((annotate("private static")))

// 内部内联函数,用于 .c 文件中定义的函数
#define PRIVATE_INLINE       __STATIC_INLINE __attribute__((annotate("private inline")))

// 公共内联函数,用于在头文件中定义的函数
#define PUBLIC_INLINE        __STATIC_INLINE __attribute__((annotate("public inline")))

/* ---------------------------------------------------- 内存分配公共接口 ---------------------------------------------------- */

/**
 * @brief 内存分配类型
 * 
 */
typedef enum alloc_type
{
    ALLOC_STATIC = 0,   // 静态内存分配
    ALLOC_DYNAMIC,      // 动态内存分配
}alloc_type_t;

/**
 * @brief 内存分配结果类型
 * 
 */
typedef enum alloc_result_type
{
    ALLOC_RESULT_SUCCESS = 0,           // 成功
    ALLOC_RESULT_OUT_PTR_INVALID,       // 输出指针无效
    ALLOC_RESULT_STATIC_BUFFER_NULL,    // 静态内存分配失败
    ALLOC_RESULT_DYNAMIC_BUFFER_NULL,   // 动态内存分配失败
    ALLOC_ALLOC_TYPE_INVALID,           // 内存分配类型无效

    ALLOC_RESULT_FREE_PTR_INVALID,      // 释放指针无效
}alloc_result_type_t;

/**
 * @brief 内存分配
 * 
 * @param alloc_type 内存分配类型
 * @param out_ptr 输出指针
 * @param byte_size 动态内存大小,仅在动态内存分配时有效,其他情况为0
 * @param static_buffer 静态内存缓冲区
 * @return alloc_result_type_t 内存分配结果类型
 */
PUBLIC alloc_result_type_t memory_alloc(alloc_type_t alloc_type, void **out_ptr, size_t byte_size, void *static_buffer)
{
    if (out_ptr == NULL)
    {
        return ALLOC_RESULT_OUT_PTR_INVALID;
    }

    *out_ptr = NULL;

    switch (alloc_type)
    {
    case ALLOC_STATIC:
        if (static_buffer == NULL)
        {
            *out_ptr = NULL;
            return ALLOC_RESULT_STATIC_BUFFER_NULL;
        }

        *out_ptr = static_buffer;
        break;
    case ALLOC_DYNAMIC:
        *out_ptr = malloc(byte_size);
        
        if (*out_ptr == NULL)
        {
            return ALLOC_RESULT_DYNAMIC_BUFFER_NULL;
        }
        break;
    default:
        return ALLOC_ALLOC_TYPE_INVALID;
    }

    return ALLOC_RESULT_SUCCESS;
}

/**
 * @brief 内存释放
 * 
 * @param alloc_type 内存分配类型
 * @param ptr 内存指针
 * @return alloc_result_type_t 内存释放结果类型
 */
PUBLIC alloc_result_type_t memory_free(alloc_type_t alloc_type, void **ptr)
{
    if (ptr == NULL || *ptr == NULL)
    {
        return ALLOC_RESULT_FREE_PTR_INVALID;
    }

    switch (alloc_type)
    {
    case ALLOC_STATIC:
        *ptr = NULL;
        break;
    case ALLOC_DYNAMIC:
        free(*ptr);
        *ptr = NULL;
        break;
    default:
        return ALLOC_ALLOC_TYPE_INVALID;
    }

    return ALLOC_RESULT_SUCCESS;
}

/* ------------------------------------------------------ 接口结构体 ----------------------------------------------------- */

// 数据操作接口
typedef struct intface_data_operation
{
    bool (*write)(void *self, uint8_t *in_data, uint16_t len);
    bool (*send)(void *self);
    bool (*receive)(void *self);
}intface_data_operation_t;

/**
 * @brief 清理数据操作接口,将所有成员变量设置为0值
 * 
 * @param ptr 数据操作接口指针
 * @return None
 */
PUBLIC_INLINE void intface_data_clean(intface_data_operation_t *ptr)
{
    if (ptr == NULL)
    {
        return;
    }

    memset(ptr, 0, sizeof(intface_data_operation_t));
}

/* ----------------------------------------------------- 对象相关定义 ----------------------------------------------------- */

typedef struct Data
{
    PUBLIC_CONST uint8_t *pubc_data;    // 数据缓冲区指针
    PUBLIC_CONST uint16_t pubc_len;     // 数据缓冲区大小

    PUBLIC intface_data_operation_t publ_operation; // 数据操作接口
}Data_t;

#define STATIC_ALLOC

#define DATA_BUFF_SIZE 128

#ifdef STATIC_ALLOC
PRIVATE_STATIC Data_t s_data;
PRIVATE_STATIC uint8_t s_buffer[DATA_BUFF_SIZE];
#endif
PRIVATE_STATIC Data_t *s_ptr_data = NULL;

PRIVATE_STATIC bool write_data(void *self, uint8_t *data, uint16_t len)
{
    if (!self || !data || len == 0 || len > DATA_BUFF_SIZE) return false;

    Data_t *ptr_data = (Data_t *)self;
    memcpy(ptr_data->pubc_data, data, len);
    ptr_data->pubc_len += len;

    printf("write_data: %*s\n", len, data);
    return true;
}

PRIVATE_STATIC bool send_data(void *self)
{
    if (!self) return false;

    Data_t *ptr_data = (Data_t *)self;

    printf("send_data: %*s\n", ptr_data->pubc_len, ptr_data->pubc_data);
    return true;
}

PRIVATE_STATIC bool receive_data(void *self)
{
    if (!self) return false;

    Data_t *ptr_data = (Data_t *)self;

    memcpy(ptr_data->pubc_data, "hello world", 11);
    ptr_data->pubc_len = 11;

    printf("receive_data: %*s\n", ptr_data->pubc_len, ptr_data->pubc_data);
    return true;
}

/**
 * @brief 获取数据对象实例,单例模式实现
 * 
 * @return Data_t* 数据对象实例指针
 */
PUBLIC Data_t *data_get_instance(void)
{
    if (s_ptr_data) return s_ptr_data;
    
#ifdef STATIC_ALLOC
    memory_alloc(ALLOC_STATIC, &s_ptr_data, 0, &s_data);
    memory_alloc(ALLOC_STATIC, &s_ptr_data->pubc_data, 0, s_buffer);
#else
    memory_alloc(ALLOC_DYNAMIC, &ptr_data, sizeof(Data_t), NULL);
    memory_alloc(ALLOC_DYNAMIC, &ptr_data->pubc_data, DATA_BUFF_SIZE, NULL);
#endif

    s_ptr_data->pubc_len = 0;
    // intface_data_clean(&s_ptr_data->publ_operation);
    s_ptr_data->publ_operation.write = write_data;
    s_ptr_data->publ_operation.send = send_data;
    s_ptr_data->publ_operation.receive = receive_data;

    return s_ptr_data;
}

PUBLIC void data_clean_instance(Data_t **ptr_data)
{
    if (!ptr_data || (!*ptr_data)) return;

#ifdef STATIC_ALLOC
    memory_free(ALLOC_STATIC, &s_ptr_data->pubc_data);
    memory_free(ALLOC_STATIC, &s_ptr_data);
#else
    memory_free(ALLOC_DYNAMIC, &s_ptr_data->pubc_data);
    memory_free(ALLOC_DYNAMIC, &s_ptr_data);
#endif
    *ptr_data = NULL;
}

int main(void)
{
    Data_t *ptr_data = data_get_instance();
    ptr_data->publ_operation.write(ptr_data, "hello world xxx", 16);
    ptr_data->publ_operation.send(ptr_data);
    ptr_data->publ_operation.receive(ptr_data);

    data_clean_instance(&ptr_data);

    while (1)
    {
    }
    
    // return 0;
}
  • 写回答

1条回答 默认 最新

  • 檀越@新空间 2026-03-21 15:33
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    学习嵌入式C语言中使用面向对象(OOP)思维进行编码是一个非常有价值的技能,尤其是在资源受限的环境中。虽然C语言本身不是面向对象的语言,但可以通过结构体、函数指针和封装等方式实现类似面向对象的功能。


    一、为什么在嵌入式中使用面向对象思维是合适的?

    1. 提高代码可维护性:通过封装数据与行为,使得代码逻辑更清晰。
    2. 增强模块化:将功能划分为独立的对象,便于调试和复用。
    3. 提升可扩展性:通过接口抽象,可以方便地替换或扩展功能模块。
    4. 降低耦合度:通过“接口”定义行为,减少不同模块之间的直接依赖。

    二、什么时候建议使用面向对象的思维?

    • 项目规模较大时:需要良好的架构设计来管理复杂性。
    • 多设备/平台共用时:通过接口抽象实现跨平台兼容。
    • 代码需长期维护时:封装数据与行为,避免全局变量滥用。
    • 硬件抽象层(HAL)开发:如驱动程序、通信协议栈等。

    三、如何学习嵌入式C语言中的面向对象编程?

    1. 掌握C语言基础

    • 熟悉结构体、指针、函数指针、内存管理等核心概念。
    • 学会使用typedefstructenum等类型定义。

    2. 理解面向对象的核心思想

    • 封装:将数据和操作封装在一起。
    • 继承(模拟):通过结构体嵌套和函数指针实现。
    • 多态(模拟):通过函数指针实现不同的行为。

    3. 参考经典资料

    以下是一些推荐的学习资源:

    a. 书籍推荐

    • 《C语言程序设计》(Kernighan & Ritchie)
    • 《C陷阱与缺陷》(Andrew Koenig)
    • 《嵌入式系统设计与开发》
    • 《C语言接口与实现》(David R. Hanson)

    b. 在线资源

    c. 示例代码分析

    • 分析你公司代码中使用的结构体、函数指针、封装方式。
    • 模仿其风格编写自己的小型项目,比如:
      • 单例模式
      • 驱动抽象
      • 多态接口

    四、如何用C语言实现面向对象的特性?

    1. 封装

    • 使用结构体封装数据和操作函数。
    // 示例:一个简单的“类”定义
    typedef struct MyObject {
        int value;
        void (*print)(struct MyObject *self);
    } MyObject;
    
    // 实现方法
    void my_object_print(MyObject *self) {
        printf("Value: %d\n", self->value);
    }
    
    // 初始化
    MyObject obj = {42, my_object_print};
    obj.print(&obj); // 调用方法
    

    2. 继承(模拟)

    • 通过结构体嵌套实现继承。
    // 基类
    typedef struct Base {
        int base_val;
        void (*base_func)(struct Base *self);
    } Base;
    
    // 派生类
    typedef struct Derived {
        Base base;  // 继承基类
        int derived_val;
        void (*derived_func)(struct Derived *self);
    } Derived;
    
    // 实现派生类的方法
    void derived_func(Derived *self) {
        printf("Derived Value: %d\n", self->derived_val);
    }
    

    3. 多态(模拟)

    • 通过函数指针实现不同的行为。
    // 接口定义
    typedef struct Interface {
        void (*do_something)(void *);
    } Interface;
    
    // 实现具体行为
    void do_something_a(void *data) {
        printf("Action A\n");
    }
    
    void do_something_b(void *data) {
        printf("Action B\n");
    }
    
    // 使用
    Interface iface_a = {do_something_a};
    Interface iface_b = {do_something_b};
    
    iface_a.do_something(NULL);
    iface_b.do_something(NULL);
    

    五、你提供的代码分析与改进

    你已经尝试了单例模式的实现,以下是你的代码片段:

    typedef struct Data {
        PUBLIC_CONST uint8_t *pubc_data;    // 数据缓冲区指针
        PUBLIC_CONST uint16_t length;       // 数据长度
        intface_data_operation_t ops;       // 数据操作接口
    } Data;
    

    这是一个典型的“封装”结构体,包含数据和操作接口。

    ✅ 改进建议:

    1. 添加构造函数:用于初始化对象。
    2. 添加析构函数:用于释放资源。
    3. 提供访问器函数:避免直接暴露内部结构。

    改进后的代码示例:

    // 定义对象结构体
    typedef struct Data {
        const uint8_t *pubc_data;
        uint16_t length;
        intface_data_operation_t ops;
    } Data;
    
    // 构造函数
    Data* data_create(const uint8_t *data, uint16_t len) {
        Data *obj = (Data*)malloc(sizeof(Data));
        if (obj == NULL) return NULL;
    
        obj->pubc_data = data;
        obj->length = len;
        intface_data_clean(&obj->ops); // 清空操作接口
        return obj;
    }
    
    // 析构函数
    void data_destroy(Data *obj) {
        if (obj != NULL) {
            free(obj);
        }
    }
    
    // 访问器函数
    const uint8_t* data_get_data(Data *obj) {
        return obj ? obj->pubc_data : NULL;
    }
    
    uint16_t data_get_length(Data *obj) {
        return obj ? obj->length : 0;
    }
    

    六、总结

    | 项目 | 内容 | |------|------| | 是否适合使用面向对象思维 | ✅ 是,特别是在大型项目中 | | 学习路径 | 从基础C语言 → 结构体与函数指针 → 封装、继承、多态模拟 | | 推荐资源 | 书籍、嵌入式论坛、开源项目、公司代码 | | 关键技巧 | 使用结构体封装数据 + 函数指针模拟方法 |


    如果你有更多具体的代码问题或想了解某个模块的实现细节,欢迎继续提问!

    评论

报告相同问题?

问题事件

  • 创建了问题 3月21日