news 2026/5/11 3:16:04

【C++ -Day7】封装实战 | 用类封装日志、配置和文件操作模块

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++ -Day7】封装实战 | 用类封装日志、配置和文件操作模块

引言

封装是面向对象三大特性(封装、继承、多态)中最基础也最重要的一环。在嵌入式开发中,代码的安全性可维护性可复用性直接决定了项目的成败。通过封装,我们可以将数据和操作隐藏在类内部,只暴露简洁的接口,彻底告别 “全局变量满天飞” 的混乱局面。

本文将带你完成三个嵌入式开发中最高频使用模块的封装实战:日志模块、配置文件读取模块和文件操作模块。所有代码均基于 Linux C++ 实现,遵循工程化规范,可直接复用到你的项目中。


核心知识点

1. 封装的意义

  • 隐藏实现细节:外部调用者无需关心内部逻辑,只需关注接口如何使用。
  • 提高安全性:防止成员变量被意外修改,所有数据访问都通过受控接口。
  • 增强可维护性:修改内部实现(如换一种日志存储方式)时,外部调用代码完全无需改动。
  • 提升复用性:封装好的类可以在多个项目中直接复用,避免重复造轮子。

2. 封装的原则

  • 成员变量私有:所有数据成员设为private,禁止外部直接访问。
  • 接口最小化:只暴露必要的public函数,接口越简洁,耦合度越低。
  • 单一职责:一个类只负责一个功能(如日志类只负责日志,不要混进配置逻辑)。
  • 异常安全:内部处理好所有可能的异常(如文件打开失败),通过返回值或异常告知调用者。

3. 嵌入式开发常见封装场景

  • 日志模块(调试与运行记录)
  • 配置文件读取模块(参数管理)
  • 文件操作模块(数据存储与读取)
  • 设备驱动模块(硬件抽象)
  • 网络通信模块(协议封装)

工程实战

任务 1:封装日志类

需求

  • 支持分级日志(DEBUG、INFO、WARN、ERROR)
  • 支持同时输出到控制台和文件
  • 支持动态设置日志级别(如只打印 WARN 及以上)
  • 日志格式自动包含时间、级别、内容

1.1 头文件Logger.h

#ifndef LOGGER_H #define LOGGER_H #include <string> #include <fstream> #include <mutex> // 日志级别枚举 enum LogLevel { DEBUG, INFO, WARN, ERROR }; class Logger { private: LogLevel currentLevel; // 当前日志级别 std::ofstream logFile; // 文件输出流 bool consoleEnabled; // 控制台输出开关 bool fileEnabled; // 文件输出开关 mutable std::mutex logMutex; // 互斥锁,保证线程安全 // 获取当前时间字符串(格式:YYYY-MM-DD HH:MM:SS) std::string getCurrentTime() const; // 日志级别转字符串 std::string levelToString(LogLevel level) const; // 实际输出日志的内部函数 void output(LogLevel level, const std::string& message) const; public: // 构造函数:默认只开启控制台输出,级别为 INFO Logger(); // 析构函数:自动关闭文件 ~Logger(); // 禁止拷贝构造和赋值(避免文件流被多次关闭) Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; // 设置日志级别(低于该级别的日志将被忽略) void setLevel(LogLevel level); // 开启/关闭控制台输出 void enableConsoleOutput(bool enable); // 开启文件输出(append=true 表示追加写入,false 表示覆盖) bool enableFileOutput(const std::string& filename, bool append = true); // 分级日志接口 void debug(const std::string& message) const; void info(const std::string& message) const; void warn(const std::string& message) const; void error(const std::string& message) const; }; #endif // LOGGER_H

1.2 实现文件Logger.cpp

#include "Logger.h" #include <iostream> #include <iomanip> #include <chrono> #include <sstream> // 构造函数:初始化默认状态 Logger::Logger() : currentLevel(INFO), consoleEnabled(true), fileEnabled(false) {} // 析构函数:关闭文件流 Logger::~Logger() { if (logFile.is_open()) { logFile.close(); } } // 设置日志级别 void Logger::setLevel(LogLevel level) { std::lock_guard<std::mutex> lock(logMutex); currentLevel = level; } // 开启/关闭控制台输出 void Logger::enableConsoleOutput(bool enable) { std::lock_guard<std::mutex> lock(logMutex); consoleEnabled = enable; } // 开启文件输出 bool Logger::enableFileOutput(const std::string& filename, bool append) { std::lock_guard<std::mutex> lock(logMutex); if (logFile.is_open()) { logFile.close(); } std::ios_base::openmode mode = std::ios_base::out; if (append) { mode |= std::ios_base::app; } else { mode |= std::ios_base::trunc; } logFile.open(filename, mode); fileEnabled = logFile.is_open(); return fileEnabled; } // 获取当前时间字符串 std::string Logger::getCurrentTime() const { auto now = std::chrono::system_clock::now(); std::time_t nowTime = std::chrono::system_clock::to_time_t(now); std::tm localTime; #ifdef _WIN32 localtime_s(&localTime, &nowTime); #else localtime_r(&nowTime, &localTime); #endif std::ostringstream oss; oss << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S"); return oss.str(); } // 日志级别转字符串 std::string Logger::levelToString(LogLevel level) const { switch (level) { case DEBUG: return "DEBUG"; case INFO: return "INFO"; case WARN: return "WARN"; case ERROR: return "ERROR"; default: return "UNKNOWN"; } } // 内部输出函数(统一处理格式和目标) void Logger::output(LogLevel level, const std::string& message) const { if (level < currentLevel) { return; // 低于当前级别,直接忽略 } std::lock_guard<std::mutex> lock(logMutex); // 组装日志格式:[时间] [级别] 内容 std::ostringstream oss; oss << "[" << getCurrentTime() << "] " << "[" << levelToString(level) << "] " << message; std::string logStr = oss.str(); // 输出到控制台 if (consoleEnabled) { std::ostream& os = (level >= WARN) ? std::cerr : std::cout; os << logStr << std::endl; } // 输出到文件 if (fileEnabled && logFile.is_open()) { logFile << logStr << std::endl; logFile.flush(); } } // 分级日志接口实现 void Logger::debug(const std::string& message) const { output(DEBUG, message); } void Logger::info(const std::string& message) const { output(INFO, message); } void Logger::warn(const std::string& message) const { output(WARN, message); } void Logger::error(const std::string& message) const { output(ERROR, message); }

任务 2:封装配置文件读取类

需求

  • 支持读取标准 INI 格式配置文件([Section]+Key=Value
  • 支持获取字符串、整数、浮点数类型配置
  • 支持默认值(配置项不存在时返回默认值)
  • 支持运行时重新加载配置文件

2.1 头文件Config.h

#ifndef CONFIG_H #define CONFIG_H #include <string> #include <map> #include <mutex> class Config { private: // 存储结构:map<Section, map<Key, Value>> using SectionMap = std::map<std::string, std::string>; std::map<std::string, SectionMap> data; mutable std::mutex configMutex; std::string filename; // 去除字符串首尾空白 std::string trim(const std::string& str) const; // 解析一行配置 bool parseLine(const std::string& line, std::string& currentSection); public: Config(); ~Config() = default; // 禁止拷贝 Config(const Config&) = delete; Config& operator=(const Config&) = delete; // 加载配置文件 bool load(const std::string& filepath); // 重新加载(用于配置文件更新后) bool reload(); // 获取配置项(带默认值) std::string getString(const std::string& section, const std::string& key, const std::string& defaultValue = "") const; int getInt(const std::string& section, const std::string& key, int defaultValue = 0) const; double getDouble(const std::string& section, const std::string& key, double defaultValue = 0.0) const; }; #endif // CONFIG_H

2.2 实现文件Config.cpp

#include "Config.h" #include <fstream> #include <sstream> #include <algorithm> Config::Config() {} // 去除首尾空白 std::string Config::trim(const std::string& str) const { auto start = str.find_first_not_of(" \t\r\n"); auto end = str.find_last_not_of(" \t\r\n"); if (start == std::string::npos) return ""; return str.substr(start, end - start + 1); } // 解析一行配置 bool Config::parseLine(const std::string& line, std::string& currentSection) { std::string trimmed = trim(line); // 忽略空行和注释(以 # 或 ; 开头) if (trimmed.empty() || trimmed[0] == '#' || trimmed[0] == ';') { return true; } // 解析 [Section] if (trimmed.front() == '[' && trimmed.back() == ']') { currentSection = trim(trimmed.substr(1, trimmed.size() - 2)); return true; } // 解析 Key=Value size_t pos = trimmed.find('='); if (pos != std::string::npos) { std::string key = trim(trimmed.substr(0, pos)); std::string value = trim(trimmed.substr(pos + 1)); if (!key.empty() && !currentSection.empty()) { data[currentSection][key] = value; } return true; } return false; // 格式错误 } // 加载配置文件 bool Config::load(const std::string& filepath) { std::lock_guard<std::mutex> lock(configMutex); std::ifstream file(filepath); if (!file.is_open()) { return false; } data.clear(); filename = filepath; std::string currentSection; std::string line; while (std::getline(file, line)) { parseLine(line, currentSection); } file.close(); return true; } // 重新加载 bool Config::reload() { if (filename.empty()) { return false; } return load(filename); } // 获取字符串配置 std::string Config::getString(const std::string& section, const std::string& key, const std::string& defaultValue) const { std::lock_guard<std::mutex> lock(configMutex); auto secIt = data.find(section); if (secIt != data.end()) { auto keyIt = secIt->second.find(key); if (keyIt != secIt->second.end()) { return keyIt->second; } } return defaultValue; } // 获取整数配置 int Config::getInt(const std::string& section, const std::string& key, int defaultValue) const { std::string value = getString(section, key, ""); if (value.empty()) return defaultValue; try { return std::stoi(value); } catch (...) { return defaultValue; } } // 获取浮点数配置 double Config::getDouble(const std::string& section, const std::string& key, double defaultValue) const { std::string value = getString(section, key, ""); if (value.empty()) return defaultValue; try { return std::stod(value); } catch (...) { return defaultValue; } }

任务 3:封装文件操作类

需求

  • 封装文件的打开、关闭、读、写操作
  • 支持文件是否存在判断、大小获取、删除
  • 使用 RAII 管理资源(自动关闭文件)

3.1 头文件FileHandler.h

#ifndef FILEHANDLER_H #define FILEHANDLER_H #include <string> #include <fstream> #include <mutex> class FileHandler { private: std::fstream file; std::string filePath; mutable std::mutex fileMutex; public: FileHandler(); ~FileHandler(); // 禁止拷贝 FileHandler(const FileHandler&) = delete; FileHandler& operator=(const FileHandler&) = delete; // 打开文件(mode: in/out/ate/app/trunc/binary) bool open(const std::string& path, std::ios_base::openmode mode); // 关闭文件 void close(); // 检查文件是否打开 bool isOpen() const; // 写入数据 bool write(const std::string& data); bool writeLine(const std::string& data); // 读取数据 bool readAll(std::string& data); bool readLine(std::string& line); // 静态工具函数(无需打开文件即可使用) static bool exists(const std::string& path); static size_t size(const std::string& path); static bool remove(const std::string& path); }; #endif // FILEHANDLER_H

3.2 实现文件FileHandler.cpp

#include "FileHandler.h" #include <sys/stat.h> #include <unistd.h> FileHandler::FileHandler() {} FileHandler::~FileHandler() { close(); } bool FileHandler::open(const std::string& path, std::ios_base::openmode mode) { std::lock_guard<std::mutex> lock(fileMutex); if (file.is_open()) { file.close(); } file.open(path, mode); filePath = path; return file.is_open(); } void FileHandler::close() { std::lock_guard<std::mutex> lock(fileMutex); if (file.is_open()) { file.close(); filePath.clear(); } } bool FileHandler::isOpen() const { std::lock_guard<std::mutex> lock(fileMutex); return file.is_open(); } bool FileHandler::write(const std::string& data) { std::lock_guard<std::mutex> lock(fileMutex); if (!file.is_open()) return false; file << data; return file.good(); } bool FileHandler::writeLine(const std::string& data) { return write(data + "\n"); } bool FileHandler::readAll(std::string& data) { std::lock_guard<std::mutex> lock(fileMutex); if (!file.is_open()) return false; file.seekg(0, std::ios::end); size_t size = file.tellg(); file.seekg(0, std::ios::beg); data.resize(size); file.read(&data[0], size); return file.good(); } bool FileHandler::readLine(std::string& line) { std::lock_guard<std::mutex> lock(fileMutex); if (!file.is_open()) return false; return std::getline(file, line).good(); } // 静态工具函数实现 bool FileHandler::exists(const std::string& path) { struct stat buffer; return (stat(path.c_str(), &buffer) == 0); } size_t FileHandler::size(const std::string& path) { struct stat buffer; if (stat(path.c_str(), &buffer) == 0) { return buffer.st_size; } return 0; } bool FileHandler::remove(const std::string& path) { return (unlink(path.c_str()) == 0); }

今日踩坑指南

坑点 1:接口设计过度暴露内部细节

场景:一开始把日志类的std::ofstream直接设为 public,导致外部代码可以随意操作文件流,最终引发数据竞争和文件损坏。解决:严格将所有成员变量设为 private,只通过 public 函数间接操作。例如文件的打开关闭通过enableFileOutput()控制,而不是直接暴露文件流。

坑点 2:忽略线程安全

场景:多线程环境下同时写日志,导致日志内容交错混乱。解决:在所有成员函数中加std::lock_guard<std::mutex>保护共享数据。注意mutable关键字的使用(允许在 const 函数中修改 mutex)。

坑点 3:异常处理缺失

场景:配置文件不存在时直接崩溃,而不是优雅地返回默认值。解决:在Config::load()中检查文件是否打开成功,在getInt()等函数中捕获std::stoi可能抛出的异常,确保程序不会因配置错误而终止。


今日总结

通过今天的实战,我们完成了三个核心模块的封装:

  1. 日志类:实现了分级、多目标输出,解决了嵌入式调试信息的规范化记录问题。
  2. 配置类:实现了 INI 文件解析,支持运行时重载,告别了硬编码参数。
  3. 文件操作类:封装了底层文件系统调用,通过 RAII 保证资源安全释放。

这三个类的共同特点是:无全局变量、接口简洁、线程安全、可直接复用。将它们应用到你的嵌入式项目中,代码质量将得到质的提升。


后续预告

下一篇文章: 【C++ -Day8】 继承 | 抽象公共能力,提高代码复用性

我们将在今天封装的基础上,通过继承抽象出公共接口(如 “输出设备” 接口),实现日志模块的进一步解耦(例如支持同时输出到串口、网络、文件)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 3:16:04

AI编码助手经验治理:ExperienceEngine解决重复错误与智能进化

1. 项目概述&#xff1a;为编码智能体引入“经验治理层”如果你和我一样&#xff0c;长期使用像 Claude Code、Cursor 或 OpenClaw 这类 AI 编码助手&#xff0c;肯定会遇到一个让人头疼的问题&#xff1a;同一个项目里&#xff0c;AI 助手会反复犯下几乎一模一样的错误。比如&…

作者头像 李华
网站建设 2026/5/11 3:15:32

2018-11至2025-9 71个主要城市商品房租金均价数据(xlsx)

01、数据介绍 本数据聚焦于全国71个主要城市的商品房租金情况&#xff0c;详细记录了从2018年11月到2025年9月期间的月度租金均价。尽管在统计过程中&#xff0c;部分城市的部分月份数据存在少量缺失情况&#xff0c;但整体数据完整度较高&#xff0c;能够较为全面地反映这一时…

作者头像 李华
网站建设 2026/5/11 3:14:31

物流分拣系统:C# + YOLOv12实现快递面单信息提取与包裹体积测量

随着电商行业的爆发式增长,物流分拣已经成为制约快递时效的核心瓶颈。传统物流分拣依赖“人工扫码+人工搬运+专用体积测量仪”的模式,存在三大致命痛点:效率低(熟练工人每分钟最多分拣15件)、错误率高(平均错分率3%-5%)、成本高(一套激光体积测量仪动辄上万元,且需要配…

作者头像 李华
网站建设 2026/5/11 3:13:41

Groundhog:基于Git仓库的开发者时间自动追踪工具

1. 项目概述&#xff1a;一个面向开发者的时间管理利器如果你是一名开发者&#xff0c;或者你的工作与代码、项目、任务紧密相关&#xff0c;那么你一定对“时间都去哪儿了”这个问题深有感触。我们每天在各种编辑器、终端、浏览器标签页之间切换&#xff0c;处理着功能开发、B…

作者头像 李华
网站建设 2026/5/11 3:05:47

软考高项选老师:这6句常见话术,听懂了你就不被割

培训机构的话术&#xff0c;大同小异。拆穿了&#xff0c;就不容易被忽悠。话术1&#xff1a;“我们通过率90%以上”拆解&#xff1a; 哪个科的通过率&#xff1f;综合知识90%和论文90%是完全两个概念。样本多少人&#xff1f;10个人90%和1000个人90%含金量天差地别。反问&…

作者头像 李华
网站建设 2026/5/11 2:59:39

DSP架构设计与低功耗优化关键技术解析

1. DSP架构基础与性能挑战数字信号处理器&#xff08;DSP&#xff09;与传统微控制器在架构设计上存在本质差异。微控制器主要面向控制任务——处理输入数据、做出决策并调整输出设备状态&#xff0c;而DSP的核心使命是维持连续数据流的高效处理。这种差异直接体现在硬件架构的…

作者头像 李华