Skip to content

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 这样的构建系统来自动处理依赖项和构建步骤。

尽管 sqlite3_exec() 适用于简单的、一次性命令,但与 SQLite 交互的现代安全方式是通过预处理语句。这种方法将 SQL 逻辑与数据分离,这可以防止 SQL 注入攻击,并且对于多次运行的查询更高效。

典型的工作流程是:

  1. sqlite3_open():打开到数据库文件的连接。
  2. sqlite3_prepare_v2():将 SQL 语句编译成字节码程序。
  3. sqlite3_bind_*():将应用程序值(例如,来自变量的值)绑定到预处理语句中的参数。
  4. sqlite3_step():执行预处理语句。对于 SELECT 查询,此函数会重复调用以获取每一行。
  5. sqlite3_column_*():对于 SELECT 查询,从当前结果行中检索数据。
  6. sqlite3_finalize():销毁预处理语句对象以释放资源。
  7. 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++ 中直接使用 C API,但这不符合 C++ 惯用法。现代 C++ 开发倾向于使用 RAII(资源获取即初始化)来管理数据库连接和语句等资源,确保即使发生错误也能正确清理它们。

对于严肃的项目,请考虑使用专用的 SQLite C++ 封装库,例如:

  • SQLiteCpp:一个流行、轻量且易于使用的封装库。
  • sqlite_orm:一个仅头文件库,提供对象关系映射(Object-Relational Mapping)层。

这些库为您处理底层 C API 调用,提供类型安全的接口,并使用 C++ 构造函数和析构函数自动管理资源生命周期。这使得代码更安全、更清晰、更易于维护。

以下是一个概念性示例,说明如何在 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;
}