C++与SQLite实战:构建商品库存管理系统,掌握面向对象与数据持久化

📅 2026/7/21 5:42:12
C++与SQLite实战:构建商品库存管理系统,掌握面向对象与数据持久化
1. 项目概述与核心价值最近在整理一些老项目翻出来一个几年前用C写的商品库存管理系统。当时是为了给一个朋友的小型零售店做个简单的进销存工具没想太多就用最熟悉的C配合SQLite数据库给撸出来了。现在回头看虽然界面简陋当时用的控制台但整个架构和代码逻辑尤其是对C面向对象特性、数据库操作以及内存管理的实践现在看来依然很有嚼头。市面上很多教程要么是纯理论要么是过于庞大的企业级系统对于想通过一个完整项目来巩固C基础、理解数据持久化、并体验小型软件工程流程的朋友来说这个“麻雀虽小五脏俱全”的库存管理系统是个不错的练手选择。这个系统核心就干几件事管理商品信息增删改查、记录库存的入库和出库流水、实时计算当前库存量并能生成简单的报表。它不依赖任何复杂的图形库或框架核心就是标准C和SQLite编译环境用VS Code配合MinGW或者直接用Visual Studio都行。通过这个项目你能实战演练如何用C的类来抽象“商品”、“库存记录”这些业务实体如何设计数据库表结构并封装CRUD操作如何处理用户输入和异常以及如何组织一个多模块的C工程。无论你是刚学完C语法想找个项目练手还是有一定基础想深化对工程化编码的理解这个案例都能提供一条清晰的路径。2. 系统整体设计与架构思路2.1 需求分析与核心模块划分做任何软件项目第一步不是急着敲代码而是想清楚它要解决什么问题。对于一个商品库存管理系统核心需求非常明确商品管理能够添加新商品编号、名称、规格、进价、售价等能查询、修改和删除逻辑删除商品信息。库存操作能够进行入库采购和出库销售操作每次操作都要记录明细时间、商品、数量、操作员、关联单号等。库存查询能实时查看任意商品的当前库存数量这个数量应该是通过累计所有入库和出库记录动态计算出来的而不是一个静态字段。报表统计能按日、月或商品类别生成简单的进销存汇总报表。基于这些需求我们可以将系统划分为几个核心的类模块商品类对应数据库中的goods表属性包括商品ID、名称、规格、单位、进价、售价、状态等。库存记录类对应数据库中的inventory_records表属性包括记录ID、商品ID、操作类型入库/出库、数量、操作时间、操作员、备注等。这里的关键设计是不单独维护一个“当前库存”表而是通过聚合函数SUM(CASE WHEN typeIN THEN quantity ELSE -quantity END)来实时计算。这避免了数据不一致的风险。数据库操作类封装所有对SQLite数据库的访问逻辑包括连接、执行SQL、处理结果集。这个类要负责将C对象和数据库表记录进行映射简单的ORM思想。业务逻辑类负责协调商品管理和库存操作比如入库前检查商品是否存在出库前检查库存是否充足。用户界面类由于是控制台程序这里就是一个菜单驱动的交互模块负责接收用户输入、调用业务逻辑、并展示结果。2.2 技术选型与工具链说明为什么选C和SQLite这是基于项目特性和学习目标的权衡。C目标是深入理解面向对象、内存管理和底层操作。用C写你会被迫思考类的设计、拷贝控制、资源管理比如数据库连接、指针与引用的使用等。这些是高级语言帮你封装掉但作为开发者必须掌握的内功。同时C标准库提供的容器、字符串和流操作也足够支撑这个项目的数据处理。SQLite它是一个进程内的、无服务器的、零配置的、事务性的SQL数据库引擎。对于单机版桌面应用来说它是最佳搭档。无需安装和配置独立的数据库服务一个.db文件就是整个数据库方便打包和分发。它支持标准的SQL语法完全能满足我们这个项目的复杂查询需求如库存汇总。开发环境Visual Studio微软的旗舰IDE对C支持极好集成了调试器、编译器和数据库工具。创建“控制台应用”项目即可开始。对于新手它的智能提示和错误检查非常友好。VS Code MinGW更轻量、更灵活的选择。你需要自己配置tasks.json编译任务、launch.json调试配置和c_cpp_properties.json头文件路径。这个过程本身就是一个很好的学习环节能让你理解编译和链接的基本过程。SQLite的集成你需要下载SQLite的amalgamation源码包包含sqlite3.c和sqlite3.h或者使用预编译的二进制库。推荐将sqlite3.c直接添加到你的项目中一起编译这样最简单无需处理动态链接库。注意如果你使用VS Code网上很多教程会让你安装C/C扩展和Code Runner。Code Runner对于单文件简单运行很方便但对于多文件项目强烈建议配置tasks.json使用g或clang进行编译并正确链接SQLite库。3. 核心类设计与数据库建模详解3.1 数据库表结构设计数据库是系统的基石设计好坏直接影响程序的复杂度和性能。我们主要设计两张核心表。商品表CREATE TABLE goods ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 主键自增 code VARCHAR(50) UNIQUE NOT NULL, -- 商品编码唯一 name VARCHAR(100) NOT NULL, -- 商品名称 specification VARCHAR(200), -- 规格型号 unit VARCHAR(20), -- 单位如个、箱 purchase_price DECIMAL(10, 2), -- 进价 sale_price DECIMAL(10, 2), -- 售价 is_active BOOLEAN DEFAULT 1 -- 是否有效逻辑删除标志 );设计要点id作为主键用于其他表的外键关联。code商品编码设唯一约束这是业务上的唯一标识比用id更符合实际。价格字段使用DECIMAL类型避免浮点数精度问题。is_active用于逻辑删除值为0时表示该商品已下架但历史记录仍可追溯。库存记录表CREATE TABLE inventory_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, goods_id INTEGER NOT NULL, -- 关联商品ID operation_type VARCHAR(10) NOT NULL, -- 操作类型IN入库/ OUT出库 quantity INTEGER NOT NULL, -- 数量正数 operator VARCHAR(50), -- 操作员 operation_time DATETIME DEFAULT (datetime(now, localtime)), -- 操作时间默认当前时间 reference_number VARCHAR(100), -- 关联单号如采购单号、销售单号 notes TEXT, -- 备注 FOREIGN KEY (goods_id) REFERENCES goods(id) ON DELETE RESTRICT );设计要点operation_type用字符串枚举清晰直观。查询时用CASE WHEN区分正负。operation_time默认值设为SQLite的本地时间确保时间记录的一致性。FOREIGN KEY外键约束保证了数据的参照完整性不能引用一个不存在的商品。ON DELETE RESTRICT表示如果商品有库存记录则禁止删除该商品强制你先处理历史数据。没有“当前库存”字段当前库存通过视图或查询实时计算。这是核心设计保证了数据源唯一永不矛盾。-- 创建一个视图方便查询当前库存 CREATE VIEW current_inventory AS SELECT g.id, g.code, g.name, g.unit, SUM(CASE WHEN ir.operation_type IN THEN ir.quantity ELSE -ir.quantity END) AS current_quantity FROM goods g LEFT JOIN inventory_records ir ON g.id ir.goods_id WHERE g.is_active 1 GROUP BY g.id, g.code, g.name, g.unit;3.2 C实体类与数据库映射接下来我们用C类来映射这些表。这里会用到一些面向对象的设计模式思想。商品类// goods.h #ifndef GOODS_H #define GOODS_H #include string class Goods { public: Goods(); // 默认构造函数 Goods(int id, const std::string code, const std::string name, /* ... */); // 带参构造 // Getter和Setter int getId() const { return id_; } void setId(int id) { id_ id; } std::string getCode() const { return code_; } void setCode(const std::string code) { code_ code; } // ... 其他属性的Getter/Setter // 业务方法 bool isValid() const; // 检查商品信息是否有效如编码非空 void deactivate(); // 逻辑删除商品 private: int id_; std::string code_; std::string name_; std::string specification_; std::string unit_; double purchase_price_; double sale_price_; bool is_active_; }; #endif // GOODS_H库存记录类类似但注意其goods_id是外键。我们还需要一个枚举来表示操作类型// inventory_record.h enum class OperationType { IN, OUT }; std::string operationTypeToString(OperationType type); OperationType stringToOperationType(const std::string str);3.3 数据库操作类的封装这是最关键的类负责所有底层SQL交互。我们要避免在业务代码中到处写SQL字符串而是通过这个类提供清晰的接口。// database.h #ifndef DATABASE_H #define DATABASE_H #include string #include vector #include memory // for std::unique_ptr #include sqlite3.h // SQLite C接口 class Goods; class InventoryRecord; class Database { public: // 单例模式确保全局只有一个数据库连接简化处理 static Database getInstance(); bool open(const std::string dbPath); void close(); // 商品相关操作 bool addGoods(const Goods goods); bool updateGoods(const Goods goods); bool deleteGoodsLogic(int goodsId); // 逻辑删除 std::unique_ptrGoods getGoodsById(int id); std::unique_ptrGoods getGoodsByCode(const std::string code); std::vectorGoods getAllGoods(bool activeOnly true); // 库存记录相关操作 bool addInventoryRecord(const InventoryRecord record); std::vectorInventoryRecord getRecordsByGoodsId(int goodsId, const std::string startTime , const std::string endTime ); int getCurrentQuantity(int goodsId); // 计算指定商品当前库存 // 事务支持 bool beginTransaction(); bool commitTransaction(); bool rollbackTransaction(); private: Database() default; // 私有构造函数 ~Database(); Database(const Database) delete; Database operator(const Database) delete; sqlite3* db_ nullptr; // 辅助函数执行非查询SQL bool executeSQL(const std::string sql); // 辅助函数绑定参数到sqlite3_stmt void bindGoodsToStatement(sqlite3_stmt* stmt, const Goods goods); Goods createGoodsFromRow(sqlite3_stmt* stmt); // ... 类似的bind和create函数用于InventoryRecord };实现要点单例模式对于这种小型桌面应用一个全局数据库连接足够。单例模式确保它被安全地共享。使用智能指针getGoodsById返回std::unique_ptrGoods明确表示调用者获得对象的所有权避免内存泄漏。参数化查询所有带参数的SQL语句如INSERT INTO goods VALUES (?, ?, ...)都必须使用sqlite3_prepare_v2和sqlite3_bind_*系列函数绝对不要用字符串拼接来构造SQL这是防止SQL注入攻击的铁律。错误处理每个数据库操作函数都应检查返回值并通过sqlite3_errmsg获取详细错误信息记录到日志或输出到控制台。事务对于入库、出库这种需要同时更新库存记录和可能更新其他数据的操作必须使用事务来保证原子性。beginTransaction、commitTransaction、rollbackTransaction这三个函数就是为此准备的。4. 核心业务流程与代码实现4.1 商品管理模块实现商品管理主要包括增、删、改、查。我们以“添加商品”为例看一个完整的业务流程如何串联各个类。// goods_service.h (业务逻辑层) class GoodsService { public: GoodsService(Database db) : db_(db) {} bool addNewGoods(const std::string code, const std::string name, double purchasePrice, double salePrice) { // 1. 参数校验 if (code.empty() || name.empty()) { std::cerr 错误商品编码和名称不能为空。\n; return false; } if (purchasePrice 0 || salePrice 0) { std::cerr 错误价格不能为负数。\n; return false; } // 2. 检查编码是否重复 auto existing db_.getGoodsByCode(code); if (existing ! nullptr) { std::cerr 错误商品编码 code 已存在。\n; return false; } // 3. 创建商品对象 Goods newGoods; newGoods.setCode(code); newGoods.setName(name); newGoods.setPurchasePrice(purchasePrice); newGoods.setSalePrice(salePrice); newGoods.setUnit(个); // 默认单位 newGoods.setActive(true); // 4. 调用数据库层保存 if (db_.addGoods(newGoods)) { std::cout 成功添加商品: name ( code )\n; return true; } else { std::cerr 添加商品到数据库失败。\n; return false; } } // ... 其他方法updateGoods, deleteGoods, searchGoods等 private: Database db_; };实操心得业务逻辑与数据访问分离GoodsService类不直接接触SQL它只负责业务规则如校验、查重然后调用Database类。这使得代码更清晰也便于单元测试可以Mock数据库层。错误信息友好给用户的错误提示要具体比如“编码已存在”而不是笼统的“操作失败”。默认值处理像unit这类非核心必填字段在业务层设置合理的默认值提升用户体验。4.2 库存操作模块实现库存操作是系统的核心尤其是出库操作需要检查库存是否充足这涉及到查询和更新的原子性。// inventory_service.h class InventoryService { public: InventoryService(Database db) : db_(db) {} bool stockIn(int goodsId, int quantity, const std::string operatorName, const std::string refNo ) { if (quantity 0) { std::cerr 错误入库数量必须为正数。\n; return false; } // 检查商品是否存在且有效 auto goods db_.getGoodsById(goodsId); if (goods nullptr || !goods-isActive()) { std::cerr 错误商品不存在或已下架。\n; return false; } InventoryRecord record; record.setGoodsId(goodsId); record.setOperationType(OperationType::IN); record.setQuantity(quantity); record.setOperator(operatorName); record.setReferenceNumber(refNo); record.setOperationTime(getCurrentDateTime()); // 获取当前时间字符串 return db_.addInventoryRecord(record); } bool stockOut(int goodsId, int quantity, const std::string operatorName, const std::string refNo ) { if (quantity 0) { std::cerr 错误出库数量必须为正数。\n; return false; } auto goods db_.getGoodsById(goodsId); if (goods nullptr || !goods-isActive()) { std::cerr 错误商品不存在或已下架。\n; return false; } // **关键步骤检查库存是否充足** int currentQty db_.getCurrentQuantity(goodsId); if (currentQty quantity) { std::cerr 错误库存不足。商品当前库存为 currentQty 出库需求为 quantity 。\n; return false; } // 使用事务确保数据一致性特别是高并发场景下但本例为单机控制台程序演示其用法 if (!db_.beginTransaction()) { std::cerr 开始事务失败。\n; return false; } InventoryRecord record; record.setGoodsId(goodsId); record.setOperationType(OperationType::OUT); record.setQuantity(quantity); record.setOperator(operatorName); record.setReferenceNumber(refNo); record.setOperationTime(getCurrentDateTime()); bool success db_.addInventoryRecord(record); if (success) { db_.commitTransaction(); std::cout 成功出库 quantity 个商品(ID: goodsId )。\n; } else { db_.rollbackTransaction(); std::cerr 出库操作失败已回滚。\n; } return success; } private: Database db_; std::string getCurrentDateTime() { /* 返回格式化的当前时间字符串 */ } };注意事项库存检查出库前的库存检查是必须的。虽然我们的数据模型通过记录计算库存理论上不会出现负数但业务上必须在操作前给出明确提示。事务的使用在stockOut中我们演示了事务的用法。尽管在这个简单的单用户控制台程序中并发冲突概率极低但养成在涉及多个步骤的数据库操作中使用事务的习惯对编写健壮的程序至关重要。想象一下如果addInventoryRecord成功了但后续的日志记录失败了没有事务就会导致数据不一致。时间处理操作时间可以在C层生成如上例也可以在SQL语句中用DEFAULT CURRENT_TIMESTAMP。前者更灵活可以统一时区处理后者更简单依赖数据库服务器时间。根据需求选择。4.3 库存查询与报表生成库存查询的核心就是执行我们之前定义的current_inventory视图对应的SQL。// 在Database类中实现 int Database::getCurrentQuantity(int goodsId) { std::string sql SELECT current_quantity FROM current_inventory WHERE id ?; sqlite3_stmt* stmt nullptr; int currentQty 0; if (sqlite3_prepare_v2(db_, sql.c_str(), -1, stmt, nullptr) SQLITE_OK) { sqlite3_bind_int(stmt, 1, goodsId); if (sqlite3_step(stmt) SQLITE_ROW) { currentQty sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); } else { // 处理错误 std::cerr 查询库存失败: sqlite3_errmsg(db_) std::endl; } return currentQty; // 如果没找到记录返回0 } // 更复杂的报表查询查询某时间段内所有商品的出入库汇总 std::vectorInventorySummary Database::getInventorySummary(const std::string startDate, const std::string endDate) { std::vectorInventorySummary summary; std::string sql R( SELECT g.id, g.code, g.name, SUM(CASE WHEN ir.operation_type IN THEN ir.quantity ELSE 0 END) as total_in, SUM(CASE WHEN ir.operation_type OUT THEN ir.quantity ELSE 0 END) as total_out FROM goods g LEFT JOIN inventory_records ir ON g.id ir.goods_id AND ir.operation_time BETWEEN ? AND ? WHERE g.is_active 1 GROUP BY g.id, g.code, g.name ); // ... 使用sqlite3_prepare_v2, sqlite3_bind_text, sqlite3_step循环获取结果集 // 将每一行数据构造成InventorySummary对象加入vector return summary; }报表设计思路报表的本质就是更复杂的SQL查询。你需要先想清楚业务想看什么数据比如“本月各商品的入库总量、出库总量、期末结存”然后将其翻译成SQL的SELECT语句结合SUM、COUNT、GROUP BY、WHERE、BETWEEN等子句。在C中你需要定义一个新的数据结构如InventorySummary来承载查询结果。5. 控制台界面与工程组织5.1 菜单驱动的用户界面对于控制台程序一个清晰的菜单系统是用户体验的关键。// main.cpp #include iostream #include limits #include goods_service.h #include inventory_service.h // ... 其他头文件 void displayMainMenu() { std::cout \n 商品库存管理系统 \n; std::cout 1. 商品管理\n; std::cout 2. 入库操作\n; std::cout 3. 出库操作\n; std::cout 4. 库存查询\n; std::cout 5. 报表统计\n; std::cout 0. 退出系统\n; std::cout 请选择操作: ; } void handleGoodsManagement(Database db) { GoodsService goodsSvc(db); int choice; do { // 显示商品管理子菜单 std::cout \n--- 商品管理 ---\n; std::cout 1. 添加商品\n2. 修改商品\n3. 删除商品\n4. 查询商品\n0. 返回上级\n选择: ; std::cin choice; std::cin.ignore(std::numeric_limitsstd::streamsize::max(), \n); // 清空输入缓冲区 switch (choice) { case 1: { std::string code, name; double purchasePrice, salePrice; std::cout 输入商品编码: ; std::getline(std::cin, code); std::cout 输入商品名称: ; std::getline(std::cin, name); std::cout 输入商品进价: ; std::cin purchasePrice; std::cout 输入商品售价: ; std::cin salePrice; goodsSvc.addNewGoods(code, name, purchasePrice, salePrice); break; } // ... 其他case case 0: return; default: std::cout 无效选择请重新输入。\n; } } while (choice ! 0); } int main() { // 初始化数据库 Database db Database::getInstance(); if (!db.open(inventory.db)) { std::cerr 无法打开数据库程序退出。\n; return -1; } std::cout 数据库连接成功。\n; // 初始化服务类 GoodsService goodsSvc(db); InventoryService invSvc(db); int mainChoice 0; do { displayMainMenu(); std::cin mainChoice; std::cin.ignore(std::numeric_limitsstd::streamsize::max(), \n); switch (mainChoice) { case 1: handleGoodsManagement(db); break; case 2: handleStockIn(invSvc); break; // 需要实现 case 3: handleStockOut(invSvc); break; // 需要实现 case 4: handleInventoryQuery(db); break; // 需要实现 case 5: handleReport(db); break; // 需要实现 case 0: std::cout 感谢使用再见\n; break; default: std::cout 无效选择请重新输入。\n; } } while (mainChoice ! 0); db.close(); return 0; }界面设计技巧输入缓冲区的清理混合使用std::cin 和std::getline时一定要在后使用std::cin.ignore清除残留的换行符否则getline会直接读到空行。错误恢复对于非数字输入std::cin 会进入错误状态。更健壮的做法是读取整行字符串然后用std::stoi转换并捕获异常。模块化每个功能模块如商品管理都有独立的处理函数main函数只负责总控菜单和调度结构清晰。5.2 多文件工程的组织与编译一个健康的C项目应该将声明和实现分离并合理组织目录结构。inventory_management/ ├── include/ // 头文件 │ ├── database.h │ ├── goods.h │ ├── goods_service.h │ ├── inventory_record.h │ └── inventory_service.h ├── src/ // 源文件 │ ├── database.cpp │ ├── goods.cpp │ ├── goods_service.cpp │ ├── inventory_record.cpp │ ├── inventory_service.cpp │ └── main.cpp ├── lib/ // 第三方库如sqlite3.c │ └── sqlite3.c ├── CMakeLists.txt // CMake构建脚本 └── README.md使用CMake构建推荐cmake_minimum_required(VERSION 3.10) project(InventoryManagement) set(CMAKE_CXX_STANDARD 11) # 包含头文件目录 include_directories(include) # 添加所有源文件 add_executable(inventory_management src/main.cpp src/database.cpp src/goods.cpp src/goods_service.cpp src/inventory_record.cpp src/inventory_service.cpp lib/sqlite3.c # 直接编译SQLite源码 ) # 在Windows下可能需要链接ws2_32等库Linux/Mac则不需要 if(WIN32) target_link_libraries(inventory_management ws2_32) endif()然后在项目根目录执行mkdir build cd build cmake .. cmake --build .就可以生成可执行文件了。CMake能帮你自动处理不同平台Windows/Linux/macOS的编译差异。6. 常见问题、调试技巧与扩展方向6.1 编译与链接问题排查“undefined reference to sqlite3_xxx”原因编译器找到了sqlite3.h头文件但链接器找不到sqlite3库的实现。解决方案A推荐将sqlite3.c源文件直接加入你的项目一起编译如上文CMake所示。方案B使用预编译的库文件如sqlite3.lib或libsqlite3.a。需要在CMake或编译命令中指定库文件路径和链接库名。例如在g中g main.cpp -o app -L/path/to/lib -lsqlite3。“无法打开数据库文件”原因sqlite3_open函数指定的路径不存在或程序没有写入权限。解决使用相对路径如./data.db或绝对路径。确保程序运行的工作目录正确或者先检查路径是否存在不存在则创建。在Database::open函数中加入更详细的错误日志。中文乱码原因控制台编码、C字符串编码、数据库编码不一致。解决Windows控制台在程序开头执行system(chcp 65001);切换到UTF-8代码页。确保源代码文件保存为UTF-8 with BOM格式对于MSVC。数据库SQLite默认UTF-8一般没问题。插入数据时确保字符串是UTF-8编码。跨平台考虑对于字符串处理尽量使用std::string和UTF-8。在需要输出到控制台时再做必要的转换。6.2 运行时逻辑错误调试库存计算不准排查首先检查inventory_records表中的数据是否正确。写一个简单的测试函数直接执行SELECT goods_id, SUM(...) FROM inventory_records GROUP BY goods_id与程序计算的结果对比。可能原因operation_type字段值不是严格的IN或OUTquantity字段有负数应该用正数用类型区分出入关联查询的JOIN条件写错。程序崩溃Segmentation Fault最常见原因空指针解引用。检查所有从数据库查询返回的std::unique_ptr或裸指针在使用前是否判空。使用工具在Linux/macOS下用gdb在Windows下用Visual Studio的调试器设置断点单步跟踪查看变量值。内存泄漏预防尽量使用智能指针std::unique_ptr,std::shared_ptr和标准库容器std::vector,std::string避免手动new/delete。检查对于SQLite的sqlite3_stmt*确保每次sqlite3_prepare_v2后无论成功与否都有对应的sqlite3_finalize。Database类的析构函数中必须调用sqlite3_close。6.3 项目扩展方向与思考这个基础版本完成后你可以从多个方向进行扩展深化对软件工程和C的理解引入单元测试使用Google Test或Catch2框架为GoodsService、InventoryService等类编写单元测试。模拟数据库层测试业务逻辑的正确性。这是保证代码质量、方便重构的关键一步。数据持久化抽象目前Database类紧密耦合SQLite。可以定义一个IDataAccess接口然后创建SqliteDataAccess类来实现它。这样未来如果需要换到MySQL或PostgreSQL只需实现新的类业务逻辑层代码几乎不用改。加入日志系统使用spdlog这样的日志库将程序运行信息、错误、SQL语句等记录到文件方便线上问题追踪。设计图形界面使用Qt或wxWidgets框架为系统开发一个图形用户界面。这将涉及到线程防止UI卡顿、信号与槽、模型-视图编程等新知识。网络化与多用户将核心逻辑做成服务端可用C REST SDK或Boost.Asio提供网络API。客户端可以是另一个C程序、网页或手机App。这会引入并发、网络通信、API设计等挑战。性能优化数据库索引在goods.code,inventory_records.goods_id和operation_time上创建索引可以极大提升查询速度。连接池如果是网络服务版需要考虑数据库连接池避免频繁创建和销毁连接。缓存对于不常变的商品信息可以在内存中建立缓存如std::unordered_map减少数据库查询。从控制台到图形界面从单机到网络从基础CRUD到高性能服务这个小小的库存管理系统就像一个种子可以生长出各种形态。每一步的扩展都是对你C和软件设计能力的实质性锻炼。