C语言模拟面向对象编程方法之封装
目录
- C++ 中抽象封装
- 封装的介绍:
- 封装的用途:
- 封装使用实例
- C语言模拟面向对象抽象封装
- 模拟过程
- 实际使用
- 嵌入式开发中实际案例介绍
- 1. STM32 HAL库(芯片固件库)
- 2. FreeRTOS(开源RTOS)
- 3. LVGL(开源GUI库)
- 4. Contiki-NG(开源物联网OS)
- 5. Zephyr RTOS驱动模型
- 6. 嵌入式中间件 - Paho MQTT
- 总结
- 差异原因
- 模拟特点
C++ 中抽象封装
封装的介绍:
封装是面向对象编程的三大特性之一,它将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装优点:
- 将所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,我们可以检测数据的有效性
封装的用途:
- 保护数据完整性:通过私有成员变量和公共接口控制数据访问
- 隐藏实现细节:外部只能通过公共接口与对象交互,内部实现可以随时修改
- 提高代码可维护性:修改内部实现不会影响外部代码
- 实现数据验证:在setter方法中添加数据验证逻辑
封装使用实例
class BankAccount {
private:std::string accountNumber;double balance;std::string ownerName;// 私有方法,外部不能调用bool isValidAmount(double amount) {return amount > 0;}public:BankAccount(const std::string& accNum, const std::string& owner, double initBalance) : accountNumber(accNum), ownerName(owner), balance(initBalance) {}// 公共接口 - 获取信息std::string getAccountNumber() const { return accountNumber; }std::string getOwnerName() const { return ownerName; }double getBalance() const { return balance; }// 公共接口 - 操作bool deposit(double amount) {if (isValidAmount(amount)) {balance += amount;return true;}return false;}bool withdraw(double amount) {if (isValidAmount(amount) && balance >= amount) {balance -= amount;return true;}return false;}void displayInfo() const {std::cout << "Account: " << accountNumber << ", Owner: " << ownerName << ", Balance: $" << balance << std::endl;}
};// 使用
BankAccount account("123456", "Alice", 1000.0);
account.deposit(500.0); // 正确操作
account.withdraw(200.0); // 正确操作
// account.balance = 1000000.0; // 错误!balance是私有的
C语言模拟面向对象抽象封装
C语言本身没有类的概念,但可以通过结构体和函数指针来模拟封装。关键是通过头文件暴露公共接口,隐藏实现细节,在源文件中实现具体功能
模拟过程
// bank_account.h - 头文件只暴露公共接口
#ifndef BANK_ACCOUNT_H
#define BANK_ACCOUNT_H// 前向声明,隐藏内部结构
typedef struct BankAccount_Private BankAccount;// 创建和销毁函数
BankAccount* bank_account_create(const char* account_number, const char* owner_name, double initial_balance);
void bank_account_destroy(BankAccount* account);// 公共接口函数
const char* bank_account_get_number(const BankAccount* account);
const char* bank_account_get_owner(const BankAccount* account);
double bank_account_get_balance(const BankAccount* account);int bank_account_deposit(BankAccount* account, double amount);
int bank_account_withdraw(BankAccount* account, double amount);
void bank_account_display_info(const BankAccount* account);#endif // BANK_ACCOUNT_H
// bank_account.c - 源文件实现内部细节
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "bank_account.h"// 私有结构体定义,外部不可见
struct BankAccount_Private {char account_number[20];char owner_name[50];double balance;// 私有方法指针(可选)int (*is_valid_amount)(double amount);
};// 私有方法实现
static int is_valid_amount(double amount) {return amount > 0;
}// 创建对象
BankAccount* bank_account_create(const char* account_number, const char* owner_name, double initial_balance) {// 参数验证if (!account_number || !owner_name || initial_balance < 0) {return NULL;}BankAccount* account = (BankAccount*)malloc(sizeof(BankAccount));if (!account) {return NULL;}// 初始化成员strncpy(account->account_number, account_number, sizeof(account->account_number) - 1);account->account_number[sizeof(account->account_number) - 1] = '\0';strncpy(account->owner_name, owner_name, sizeof(account->owner_name) - 1);account->owner_name[sizeof(account->owner_name) - 1] = '\0';account->balance = initial_balance;account->is_valid_amount = is_valid_amount;return account;
}// 销毁对象
void bank_account_destroy(BankAccount* account) {if (account) {free(account);}
}// 公共接口实现
const char* bank_account_get_number(const BankAccount* account) {return account ? account->account_number : NULL;
}const char* bank_account_get_owner(const BankAccount* account) {return account ? account->owner_name : NULL;
}double bank_account_get_balance(const BankAccount* account) {return account ? account->balance : 0.0;
}int bank_account_deposit(BankAccount* account, double amount) {if (!account || !account->is_valid_amount(amount)) {return 0;}account->balance += amount;return 1;
}int bank_account_withdraw(BankAccount* account, double amount) {if (!account || !account->is_valid_amount(amount) || account->balance < amount) {return 0;}account->balance -= amount;return 1;
}void bank_account_display_info(const BankAccount* account) {if (account) {printf("Account: %s, Owner: %s, Balance: $%.2f\n",account->account_number, account->owner_name, account->balance);}
}
实际使用
// main.c - 使用封装的银行账户
#include <stdio.h>
#include "bank_account.h"int main() {// 创建账户对象BankAccount* account = bank_account_create("123456", "Alice", 1000.0);if (!account) {printf("Failed to create account!\n");return -1;}// 使用公共接口操作对象bank_account_display_info(account);// 存款和取款if (bank_account_deposit(account, 500.0)) {printf("Deposit successful!\n");} else {printf("Deposit failed!\n");}if (bank_account_withdraw(account, 200.0)) {printf("Withdrawal successful!\n");} else {printf("Withdrawal failed!\n");}// 尝试非法操作会被拒绝if (!bank_account_withdraw(account, -100.0)) {printf("Invalid withdrawal amount rejected!\n");}if (!bank_account_withdraw(account, 2000.0)) {printf("Insufficient balance withdrawal rejected!\n");}bank_account_display_info(account);// 获取信息printf("Account owner: %s\n", bank_account_get_owner(account));printf("Current balance: $%.2f\n", bank_account_get_balance(account));// 清理bank_account_destroy(account);return 0;
}
嵌入式开发中实际案例介绍
1. STM32 HAL库(芯片固件库)
// stm32f4xx_hal_gpio.h - 公共接口
typedef struct {uint32_t Pin; /*!< 指定要配置的GPIO引脚 */uint32_t Mode; /*!< 指定GPIO引脚的操作模式 */uint32_t Pull; /*!< 指定GPIO引脚的上拉/下拉 */uint32_t Speed; /*!< 指定GPIO引脚的速度 */uint32_t Alternate; /*!< 指定GPIO引脚的复用功能 */
} GPIO_InitTypeDef;// 公共函数
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin);
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);// 使用示例 - 用户只关心接口,不关心内部实现
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
2. FreeRTOS(开源RTOS)
// task.h - 任务管理公共接口
typedef void * TaskHandle_t;// 创建任务函数 - 隐藏内部任务控制块细节
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,const char * const pcName,configSTACK_DEPTH_TYPE usStackDepth,void * pvParameters,UBaseType_t uxPriority,TaskHandle_t * pvCreatedTask);// 任务管理函数
void vTaskDelete(TaskHandle_t xTaskToDelete);
void vTaskDelay(const TickType_t xTicksToDelay);
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);// 使用示例 - 用户不关心任务控制块内部结构
TaskHandle_t xTaskHandle;
xTaskCreate(vTaskFunction, "MyTask", 1000, NULL, 1, &xTaskHandle);
vTaskDelay(1000 / portTICK_PERIOD_MS);
3. LVGL(开源GUI库)
// lv_obj.h - 对象系统公共接口
typedef struct _lv_obj_t lv_obj_t;// 创建和删除函数
lv_obj_t * lv_obj_create(lv_obj_t * parent, const lv_obj_t * copy);
void lv_obj_del(lv_obj_t * obj);// 设置属性函数 - 隐藏内部属性存储细节
void lv_obj_set_size(lv_obj_t * obj, lv_coord_t w, lv_coord_t h);
void lv_obj_set_pos(lv_obj_t * obj, lv_coord_t x, lv_coord_t y);
void lv_obj_set_style(lv_obj_t * obj, lv_style_t * style);// 获取属性函数
lv_coord_t lv_obj_get_width(const lv_obj_t * obj);
lv_coord_t lv_obj_get_height(const lv_obj_t * obj);// 使用示例
lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL);
lv_obj_set_size(btn, 100, 50);
lv_obj_set_pos(btn, 10, 10);
4. Contiki-NG(开源物联网OS)
// sensor.h - 传感器抽象接口
struct sensors_sensor;// 传感器驱动结构体 - 定义标准接口
struct sensors_sensor {char *name;int (* value)(int type);int (* configure)(int type, int value);int (* status)(int type);
};// 传感器访问函数
int sensors_sensor_get_value(const struct sensors_sensor *s, int type);
int sensors_sensor_configure(struct sensors_sensor *s, int type, int value);// 具体传感器实现被封装在.c文件中
extern struct sensors_sensor tmp102_sensor;
extern struct sensors_sensor sht11_sensor;// 使用示例 - 统一接口访问不同传感器
int temp = sensors_sensor_get_value(&tmp102_sensor, SENSOR_TYPE_TEMPERATURE);
int humidity = sensors_sensor_get_value(&sht11_sensor, SENSOR_TYPE_HUMIDITY);
5. Zephyr RTOS驱动模型
// device.h - 设备驱动抽象层
typedef void (*device_pm_control_callback)(const struct device *dev,int command,void *context);// 设备结构体 - 对应用层隐藏具体实现
struct device {const char *name;const void *config;void * const data;const struct device_config *device_config;device_pm_control_callback device_control;
};// 设备操作接口
const struct device *device_get_binding(const char *name);
int device_set_power_state(const struct device *dev, uint32_t state);// 驱动特定的操作通过API结构体封装
struct uart_driver_api {int (*poll_in)(const struct device *dev, unsigned char *pChar);void (*poll_out)(const struct device *dev, unsigned char outChar);
};// 使用示例
const struct device *uart_dev = device_get_binding("UART_0");
const struct uart_driver_api *api = (const struct uart_driver_api *)uart_dev->api;
api->poll_out(uart_dev, 'A');
6. 嵌入式中间件 - Paho MQTT
// MQTTClient.h - MQTT客户端公共接口
typedef void* MQTTClient;// 创建和销毁客户端
int MQTTClient_create(MQTTClient* handle, const char* serverURI, const char* clientId, int persistence_type, void* persistence_context);
void MQTTClient_destroy(MQTTClient* handle);// 连接管理
int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options);
int MQTTClient_disconnect(MQTTClient handle, int timeout);// 消息发布和订阅
int MQTTClient_publish(MQTTClient handle, const char* topicName, int payloadlen, void* payload, int qos, int retained);
int MQTTClient_subscribe(MQTTClient handle, const char* topic, int qos);// 使用示例 - 隐藏网络通信细节
MQTTClient client;
MQTTClient_create(&client, "tcp://localhost:1883", "client1", MQTTCLIENT_PERSISTENCE_NONE, NULL);MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;MQTTClient_connect(client, &conn_opts);
MQTTClient_publish(client, "test/topic", strlen("Hello"), "Hello", 1, 0);
总结
差异原因
-
C++原生封装:通过private、protected、public关键字天然支持,编译器强制访问控制
-
C语言模拟:通过头文件分离接口和实现,在源文件中隐藏私有数据和函数
-
访问控制:C语言没有语言级别的访问控制,需要通过编程规范和组织代码来实现
模拟特点
-
头文件作为接口:只声明公共函数和抽象类型
-
源文件隐藏实现:在.c文件中定义具体结构和私有函数
-
不透明指针:使用前向声明隐藏内部数据结构
-
命名规范:通过命名约定区分公共接口和私有实现
-
编译时封装:通过静态链接隐藏实现,用户只能看到头文件声明的接口
这种封装方式在嵌入式系统中广泛应用,既保持了C语言的高效性,又获得了面向对象的封装好处,是嵌入式领域重要的设计模式。