SQLite - C/C++
SQLite - 现代 C/C++ 集成
Section titled “SQLite - 现代 C/C++ 集成”本教程涵盖了从 C 和 C++ 程序使用 SQLite 的现代推荐实践。我们将通过使用预处理语句(Prepared Statements)和适当的错误处理来关注安全性、效率和清晰度。
首先,请确保您的系统上安装了 SQLite3 开发库。在 Debian/Ubuntu 上,您可以使用 sudo apt-get install libsqlite3-dev 命令安装它。在其他系统上,包名称将类似。
要编译 C/C++ 代码,您必须链接 SQLite3 库。编译命令是:
# 对于 C$ gcc your_program.c -o your_program -lsqlite3
# 对于 C++$ g++ your_program.cpp -o your_program -lsqlite3对于实际项目,强烈建议使用像 CMake 这样的构建系统来自动处理依赖项和构建步骤。
核心 API:预处理语句
Section titled “核心 API:预处理语句”尽管 sqlite3_exec() 适用于简单的、一次性命令,但与 SQLite 交互的现代安全方式是通过预处理语句。这种方法将 SQL 逻辑与数据分离,这可以防止 SQL 注入攻击,并且对于多次运行的查询更高效。
典型的工作流程是:
sqlite3_open():打开到数据库文件的连接。sqlite3_prepare_v2():将 SQL 语句编译成字节码程序。sqlite3_bind_*():将应用程序值(例如,来自变量的值)绑定到预处理语句中的参数。sqlite3_step():执行预处理语句。对于SELECT查询,此函数会重复调用以获取每一行。sqlite3_column_*():对于SELECT查询,从当前结果行中检索数据。sqlite3_finalize():销毁预处理语句对象以释放资源。sqlite3_close():关闭数据库连接。
完整的 C 语言示例:一个完整的 CRUD 应用程序
Section titled “完整的 C 语言示例:一个完整的 CRUD 应用程序”下面是一个完整且注释良好的 C 语言程序,演示了如何使用预处理语句执行创建、读取、更新和删除(CRUD)操作。
#include <stdio.h>#include <sqlite3.h>
// 辅助函数,用于检查并打印错误void check_error(int rc, sqlite3 *db, char *action_msg) { if (rc != SQLITE_OK) { fprintf(stderr, "SQL error during %s: %s\n", action_msg, sqlite3_errmsg(db)); sqlite3_close(db); exit(1); }}
int main() { sqlite3 *db; sqlite3_stmt *stmt; char *db_file = "test.db";
// 1. 打开数据库 int rc = sqlite3_open(db_file, &db); check_error(rc, db, "database open"); printf("Database opened successfully.\n");
// 2. 创建表(这里为简化使用 sqlite3_exec) char *create_sql = "CREATE TABLE IF NOT EXISTS USERS(" "ID INTEGER PRIMARY KEY AUTOINCREMENT, " "NAME TEXT NOT NULL, " "EMAIL TEXT UNIQUE NOT NULL);"; rc = sqlite3_exec(db, create_sql, 0, 0, 0); check_error(rc, db, "table creation"); printf("Table created or already exists.\n");
// 3. 使用预处理语句插入数据(防止 SQL 注入) char *insert_sql = "INSERT INTO USERS (NAME, EMAIL) VALUES (?, ?);"; rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, 0); check_error(rc, db, "insert prepare");
// 绑定值到语句 sqlite3_bind_text(stmt, 1, "Alice", -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, "alice@example.com", -1, SQLITE_STATIC);
// 执行语句 if (sqlite3_step(stmt) != SQLITE_DONE) { fprintf(stderr, "SQL error during insert: %s\n", sqlite3_errmsg(db)); } sqlite3_finalize(stmt); // 结束语句 printf("New user inserted.\n");
// 4. 查询数据 char *select_sql = "SELECT ID, NAME, EMAIL FROM USERS;"; rc = sqlite3_prepare_v2(db, select_sql, -1, &stmt, 0); check_error(rc, db, "select prepare");
printf("\n--- User List ---\n"); // 遍历结果 while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { int id = sqlite3_column_int(stmt, 0); const unsigned char *name = sqlite3_column_text(stmt, 1); const unsigned char *email = sqlite3_column_text(stmt, 2); printf("ID: %d, Name: %s, Email: %s\n", id, name, email); } if (rc != SQLITE_DONE) { check_error(rc, db, "select step"); } sqlite3_finalize(stmt);
// 5. UPDATE 和 DELETE 操作也将遵循类似模式: // 准备 -> 绑定(例如,WHERE 子句中的 ID) -> 执行 -> 结束
// 6. 关闭数据库 sqlite3_close(db); printf("\nDatabase closed.\n");
return 0;}C++ 最佳实践
Section titled “C++ 最佳实践”尽管您可以在 C++ 中直接使用 C API,但这不符合 C++ 惯用法。现代 C++ 开发倾向于使用 RAII(资源获取即初始化)来管理数据库连接和语句等资源,确保即使发生错误也能正确清理它们。
对于严肃的项目,请考虑使用专用的 SQLite C++ 封装库,例如:
- SQLiteCpp:一个流行、轻量且易于使用的封装库。
- sqlite_orm:一个仅头文件库,提供对象关系映射(Object-Relational Mapping)层。
这些库为您处理底层 C API 调用,提供类型安全的接口,并使用 C++ 构造函数和析构函数自动管理资源生命周期。这使得代码更安全、更清晰、更易于维护。
简单的 C++ RAII 封装器示例
Section titled “简单的 C++ RAII 封装器示例”以下是一个概念性示例,说明如何在 C++ 类中封装 sqlite3 句柄以自动管理其生命周期。
#include <iostream>#include <string>#include <memory>#include <stdexcept>#include <sqlite3.h>
// sqlite3 句柄的简单 RAII 封装器class SQLiteDB {public: SQLiteDB(const std::string& db_path) { if (sqlite3_open(db_path.c_str(), &db) != SQLITE_OK) { throw std::runtime_error("Cannot open database: " + std::string(sqlite3_errmsg(db))); } }
~SQLiteDB() { if (db) { sqlite3_close(db); } }
// 阻止拷贝 SQLiteDB(const SQLiteDB&) = delete; SQLiteDB& operator=(const SQLiteDB&) = delete;
sqlite3* get() const { return db; }
private: sqlite3* db = nullptr;};
int main() { try { SQLiteDB db("test_cpp.db"); std::cout << "Database opened successfully via C++ RAII wrapper." << std::endl;
// 现在您可以使用 db.get() 获取原始指针以进行其他 API 调用 char *err_msg = nullptr; const char* sql = "CREATE TABLE IF NOT EXISTS CONTACTS(NAME TEXT);"; if (sqlite3_exec(db.get(), sql, 0, 0, &err_msg) != SQLITE_OK) { std::cerr << "SQL error: " << err_msg << std::endl; sqlite3_free(err_msg); }
} catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 1; }
// 当 db 超出作用域时,它将在此处自动关闭 return 0;}